状態を持つ(stateful)コンポーネント
関数コンポーネントの中には状態を持つコンポーネントもあります。これはステートフルなコンポーネントと呼ばれます。実際のフロントエンドの開発ではステートフルなコンポーネントをよく使います。
ステートフルなコンポーネントの特徴は以下の数式で表現されます。
UI = f(state)
先ほどの数式と同じように見えますが、propsはコンポーネントの外部から渡される値であるのに対して、stateはコンポーネント内で管理する値であることに注意が必要です。
ボタンをクリックすると表示している値が増減するカウンターコンポーネントを例にstateを説明します。stateはuseStateというReact Hooksと呼ばれる特殊な関数を使って管理します。React HooksはReactコンポーネント内からしか呼び出せません。
function Counter() {
// カウント数という「状態」とその更新関数
const [count, setCount] = useState(0);
// 更新関数を呼び出して値を増減させるイベントハンドラ
const increment = () => setCount((prevCount) => prevCount + 1);
const decrement = () => setCount((prevCount) => prevCount - 1);
return (
<div>
<p>カウント: {count}</p> // ここで現在のカウント数を表示する
<button onClick={increment}>増加</button> // onClickイベントが発火すると値が増える
<button onClick={decrement}>減少</button>
</div>
);
}
useStateの返り値は要素が2個の配列です。最初の要素には最新のステートの値が格納されており、2つ目の要素はその値の更新関数です。Reactのステートはこの更新関数を使うことでしか値を変更できません。
// const [状態, 状態の更新関数] = useState(状態の初期値); const [count, setCount] = useState(0);
increment, decrement というイベントハンドラで状態の更新関数(setCount)をラップしています。ボタンがクリックされることで increment, decrement が呼び出されたら、最初の0という値が1ずつ増減するというロジックです。
描画されるHTMLは以下です。
<div> <p>カウント: 0</p> <button>増加</button> <button>減少</button> </div>

次に増加ボタンを5回クリックしてみます。すると、カウンターの値は5になりました。描画されるHTMLは以下です。
<div> <p>カウント: 5</p> <button>増加</button> <button>減少</button> </div>

状態の更新関数(ここではsetCount)を実行すると、状態が変わります。Reactは状態が変わったことを検知すると、コンポーネントを再描画します。これを再レンダリングといいます。コンポーネントは関数であるため、再レンダリングは関数の再実行と同じです。
setCount()という更新関数が5回実行されることで、HTMLが5回再描画されました。以下のことが起きたのだと考えてもらえると良いでしょう。
UI = f(state) // コンポーネントの定義 f(0) // <p>カウント: 0</p> (初回レンダリング) f(1) // <p>カウント: 1</p> (再レンダリング) ... f(5) // <p>カウント: 5</p> (再レンダリング)
コンポーネントが初めて実行されることを初回レンダリングといいます。その後、クリックなどのイベントをきっかけにして状態が更新されれば再レンダリングが行われます。これをフローにすると以下のようになります。
UI → ユーザーのアクション → 状態の更新 → UIの更新 → ユーザーのアクション...
この単方向データフローを考案したMeta社はこのアーキテクチャをFluxと名付けました。本来はアプリケーションアーキテクチャの説明なのでReactコンポーネントの単体の説明とは異なりますが、このような考え方があるのだということをサラッと紹介しておきます。アプリケーション単位では、Viewが表示された後、Actionが発生してDispatcherが起動し、Storeが変更されたらViewが更新されるという流れで説明されています。
また、propsとsteteを両方もつコンポーネントも作成できます。例えばCounterの初期値を外部から渡せる値とした場合、コンポーネントは以下の通り記述します。
type Props = {
initialCount: number
}
function Counter({initialCount}: Props) { // props を持つ
const [count, setCount] = useState(initialCount); // state を持つ
// ...
return ( /* ... */ );
}
式では以下の通り表現されます。
UI = f(props, state)
このコンポーネントの呼び出し方は以下の通りになります。ただし、state はコンポーネント内で管理されているため、呼び出し側からCounterコンポーネントがステートを持つか持たないかは区別できませんが、ここでは特に問題ではありません。
<Counter initialCount={100} />
これで初期値が100でありボタンを更新すると1つずつ増減するカウンターができました。なお、コンポーネントはただの関数であり再利用できるため、初期値を100以外で自由に設定できる上にReact内でどこでも呼び出すことができます。
<Counter initialCount={100} />
<Counter initialCount={0} /> // 初期値が0
<Counter initialCount={30} /> // 初期値が30
また状態はコンポーネント単位で管理されるため、あるカウンターの状態更新関数が他のカウンターの数値を増減させることはありません。
