まずはStateを持ち上げる
共有したいstateを、利用するコンポーネントの共通の親へ移します。propsで値と更新関数を渡す方法は、データの流れが明確で最初に選びやすい方法です。
親から子へ渡すJSX
import { useState } from 'react';
function App() {
const [theme, setTheme] = useState('light');
return <Toolbar theme={theme} onChange={setTheme} />;
}
function Toolbar({ theme, onChange }) {
return <button onClick={() => onChange('dark')}>{theme}</button>;
}Contextを使う
多くの階層をまたいでテーマやログインユーザーを渡す場合はContextを使えます。React 19ではContextオブジェクトをそのままProviderとして書けます。古いReactではThemeContext.Providerを使います。
Contextを提供して読むJSX
import { createContext, useContext } from 'react';
const ThemeContext = createContext('light');
function Button() {
const theme = useContext(ThemeContext);
return <button className={theme}>保存</button>;
}
function App() {
return <ThemeContext value="dark"><Button /></ThemeContext>;
}Providerのvalueが変わると、そのContextを読むコンポーネントが再レンダーします。Contextへ大きなオブジェクトを何でも詰め込まず、役割ごとに分けます。
Reducerで更新する
状態更新が増えたら、更新の種類をactionとして表し、Reducerに処理を集めます。Reducerは現在のstateとactionから次のstateを返す純粋な関数です。
カウンターReducerJSX
import { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'increment': return { count: state.count + 1 };
case 'decrement': return { count: state.count - 1 };
case 'reset': return { count: 0 };
default: throw new Error('未知のaction');
}
}
const [state, dispatch] = useReducer(reducer, { count: 0 });
dispatch({ type: 'increment' });「どの操作で」「どの状態がどう変わるか」をaction名で残せるため、フォームや買い物かごなどの状態遷移をテストしやすくなります。
カスタムHook
複数のコンポーネントで同じstateと処理を使うときは、useで始まるカスタムHookへ切り出します。
useToggleJSX
import { useState } from 'react';
function useToggle(initial = false) {
const [value, setValue] = useState(initial);
const toggle = () => setValue(current => !current);
return [value, toggle];
}
function Panel() {
const [open, toggle] = useToggle();
return <><button onClick={toggle}>切替</button>{open && <p>表示中</p>}</>;
}Hookはコンポーネントや他のHookのトップレベルで呼び、条件分岐やループの中で呼びません。次はTypeScriptとNext.jsで、型とアプリ構成へ進みます。
