8.4 Reactコンポーネントプログラミング
Reactのコンポーネントの書き方には、「クラスコンポーネント」と「関数コンポーネント」の二種類がありますが、クラスコンポーネントは古典的な方法で今では関数コンポーネントとして記述するのが一般的になっています。以下の例では、Reactのコンポーネントを関数で記述しています。
コンポーネント関数の主な働きは、コンポーネントが表示する内容を作って仮想DOMに書き込むことです。コンポーネント関数の実行のことを、Reactでは「レンダー」と呼びます。下のコードの場合、function TeamsViewer()で実行される内容が「レンダー」に相当します。
/* TeamsViewer.js */
import React, { useState, useEffect } from 'react';
import './TeamsViewer.css';
import TeamList from './TeamList';
function TeamsViewer(props){
const [league, setLeague] = useState(null);
const setAction = () =>{
let element = document.querySelector("#select_league");
setLeague(element.value);
}
return(
<div className="teams_viewer">
<div className="teams_viewer_header">
<select id="select_league">
<option value="1"> J-1 </option>
<option value="2"> J-2 </option>
<option value="3"> J-3 </option>
</select>
<button className="select_button" onClick={(e)=> setAction()}> 選択 </button>
</div>
<div className="teams_viewer_body">
<TeamList league={league} />
</div>
</div>
)
}
export default TeamsViewer;
関数コンポーネントの成り立ちをイメージ図にすると、以下のようになります。

以下、関数コンポーネントを記述するためにReactが提供している機能を解説していきます。
8.4.1 JSXとDOMへの書き込み
上記の例の、最後のreturn()が仮想DOMへの書き込みを行っている部分です。書き込む内容は、return()関数の引数の中に「JSX」(JavaScript XML)という記法を使って記述します。
このJSXの書き方はHTMLにとても似ていてるのですが、いくつか違う点があります。
- 属性名の変更: HTMLの class は className、for は htmlFor になります
- キャメルケース: onclick は onClick のように、属性名がキャメルケースになります
- 閉じタグの義務: <img> や <br> は <img /> や <br /> のように閉じる必要があります
- JavaScriptの埋め込み: {} で囲むことで、JSの変数や式、配列のmap処理などを直接埋め込むことができます
- 単一のルート要素: 複数の要素を返す場合、必ず一つの親要素(<div>や<Fragment>)で囲む必要があります
8.4.2 状態変数(State variable)
状態変数は以下の書式で定義します。
const [state, setState] = useState(initialState)
- state : 変数名を定義します。
- setState : 変数に値をセットする関数名を定義します。
- initialState : 変数の初期値を定義します
例えば、以下のように使います。
import { useState } from 'react';
function MyComponent() {
const [age, setAge] = useState(42);
const [name, setName] = useState('Taylor');
// ...
通常、関数内で定義された変数は、関数が呼び出されるごとに初期化されるので、前回呼び出された時にセットされた値は次の呼び出しの時に引き継がれることはありません。これに対して、状態変数にセットした値は、コンポーネント関数の呼び出し間で引き継ぐことができます。上記の例の場合、状態変数ageに値32をセットする時には、setAge(32) を実行します。
状態付き変数の更新は、Reactが監視しており、コンポーネント関数内で定義された状態付き変数が更新されると、そのコンポーネント関数が呼び出されます。(レンダーされます)
呼び出されたコンポーネント関数の中でuseState()が呼び出されると、状態変数にセットされた値age=32はこのレンダーにも引き継がれ、JSXの中の{}で囲まれた中に状態変数や、状態変数を処理した内容を返す関数などがあれば、表示される内容が変更されて仮想DOMに書き込まれます。
このように、状態変数が変更されたことをトリガにして、表示される内容が更新されるという仕組みになっています。
8.4.3 props によるデータの受け渡し
props は、親コンポーネントから子コンポーネントにデータを渡すための手段です。例えば、親コンポーネントから
<ChildComponent name=“Taylor” age=42/>
とすると、ChildComponentにnameという引数の値が引き渡されます。
受け取る側のChildComponentは次のように書きます。
function ChildComponent(props){
return (<div>{props.name}さん, 年齢:{props.age}歳</div>);
}
コンポーネントに親から値が引き渡される場合は、関数の引数にpropsを指定します。propsには、親から引き渡されたname とage の2つのプロパティーが含まれており、props.nameのようにして引き渡された値を参照します。
子コンポーネントはpropsを受け取って表示したり、動作に利用したりしますが、propsを子コンポーネント内で直接変更することはできません。
propsもReactにより更新状態を監視されており、これも状態付き変数の一種として動作します。
冒頭に示したサンプルコードTeamsViewerを見てみましょう。
const [league, setLeague] = useState(null);
でleagueという状態付き変数を定義しています。leagueの値は、ボタンが押された時に呼ばれるイベントハンドラーsetAction()の中で、セレクターで選択されているものの値(value)がsetLanguage()
によってセットされます。例えばセレクターでJ-1が選択されていればその値(value)は1で、この値がセットされると再度レンダーが行われ、leagueの値1が引き継がれます。
<TeamList league={league} />
のところでTeamListコンポーネントが呼び出され、このコンポーネントにはleague=1のプロパティーが引き渡されます。
8.4.4 useEffectフック
useEfferct は、マウスのクリックや、キー入力などの特定のイベントによってではなく、レンダー自体によって引き起こされる副作用を指定するためのものです。副作用とは、「主たる作用」に対する言葉で、Reactにおける主作用はコンポーネントがUIを構築するためにJSXを返すことです。それ以外の目的を持った処理は全て「副作用」で、主作用とば別に(主作用の後に)実行されます。例えば外部からコンポーネントへAPIデータの取得、DOMの操作、コンポーネント内にタイマーを設置などがこれに当たります。特にuseEffectでよく行われるのは外部からコンポーネントへAPIデータの取得です。
useEffectフックは以下のように記述します。
useEffect(() => {
/* 第1引数には実行させたい副作用関数を記述*/
console.log('副作用関数が実行されました!')
},[依存する変数の配列]) // 第2引数には副作用関数の実行タイミングを制御する依存データを記述
第2引数を指定することにより、第1引数に渡された副作用関数の実行タイミングを制御することができます。
- 第2引数に空の依存配列[]を指定すると、初回レンダリング時のみ副作用関数が実行されます。
- 第2引数に状態変数のリストを指定すると、初回レンダリング時に加え状態変数に変化があった時に副作用関数が実行されます。
サンプルコードTeamListを見てみましょう。
/* TeamList.js */
import React, { useState, useEffect } from 'react';
function TeamList(props){
const [list_info, setInfo] = useState([]);
useEffect(() => {
if(props.league){
let target = "http://127.0.0.1:8000/api/v1/teams/" + props.league;
fetch(target,{
credentials: "same-origin",
})
.then(response => {
return response.json();
})
.then(result =>{
const txt = JSON.stringify(result, null,' ');
let res = JSON.parse(txt);
setInfo(res);
})
.catch(error =>{
console.error(error);
})
}
},[props]);
if(list_info.length > 0){
return (
<div className="team_list">
<table>
<thead>
<tr>
<th>チーム名</th>
<th>ロゴ</th>
</tr>
</thead>
<tbody>
{list_info.map((team, index)=>{
return(
<tr key={index}>
<td>{team['team_name']}</td>
<td> <img src={team['team_logo']} /> </td>
</tr>
)
})}
</tbody>
</table>
</div>
)
}
}
export default TeamList;
useEffect()の第2引数は、[props]となっています。これによりTeamListに渡されるプロパティーpropsに変更があった時に呼び出されるようになります。上位のコンポーネントTeamsVIewerでleagueの値が変更になりTeamListに引き渡された時に、useEffectが呼び出されます。
useEffectで定義されている関数では、バックエンドのREST APIを呼び出し、取得した値をsetInfo()によって状態変数info_listに格納します。状態変数が変わったことによってTeamsViewerが再レンダリングされます。
8.4.5 全体の流れ
ここまで出てきた、サンプルコードが実行される流れを下図に示します。
