6.1 stateに保存すべきもの
次の条件をすべて満たす値がstate候補になる。
- 時間とともに変わる
- レンダリングに影響する
- propsや既存stateから直接計算できない
派生値はstateに保存しない。
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
// stateにしない
const fullName = `${lastName} ${firstName}`.trim();
6.2 冗長なstate
悪い例:
const [items, setItems] = useState<Item[]>([]);
const [selectedItem, setSelectedItem] = useState<Item | null>(null);
itemsが更新されるとselectedItemが古くなる可能性がある。
改善:
const [selectedId, setSelectedId] = useState<string | null>(null);
const selectedItem =
items.find(item => item.id === selectedId) ?? null;
6.3 stateの正規化
入れ子が深く重複したstateは更新が難しい。
type State = {
entities: Record<string, Task>;
ids: string[];
selectedId: string | null;
};
正規化すると、単一のエンティティ更新が明確になる。
6.4 stateを引き上げる
兄弟コンポーネントが同じ値を使う場合、共通の親へ移す。
function TemperatureCalculator() {
const [celsius, setCelsius] = useState("");
return (
<>
<CelsiusInput value={celsius} onChange={setCelsius} />
<FahrenheitDisplay celsius={celsius} />
</>
);
}
6.5 useReducer
複数の関連更新をイベントとして表現したい場合に使う。
type State = {
items: Task[];
};
type Action =
| { type: "added"; task: Task }
| { type: "toggled"; id: string }
| { type: "removed"; id: string };
function reducer(state: State, action: Action): State {
switch (action.type) {
case "added":
return {
...state,
items: [...state.items, action.task],
};
case "toggled":
return {
...state,
items: state.items.map(item =>
item.id === action.id
? { ...item, completed: !item.completed }
: item
),
};
case "removed":
return {
...state,
items: state.items.filter(item => item.id !== action.id),
};
default:
return assertNever(action);
}
}
function assertNever(value: never): never {
throw new Error(`未知のaction: ${JSON.stringify(value)}`);
}
Reducerは純粋でなければならない。
6.6 Context
Contextは、深い階層へ値を渡すための仕組みである。
const ThemeContext = createContext<"light" | "dark">("light");
function App() {
return (
<ThemeContext value="dark">
<Page />
</ThemeContext>
);
}
React 19ではContext自身をProviderとして使用できる。
function Toolbar() {
const theme = use(ThemeContext);
return <button data-theme={theme}>操作</button>;
}
Contextはグローバルstate管理そのものではない。値が変わると利用側が再レンダリングされるため、更新頻度と責務を考える。
6.7 外部ストア
React外部の状態管理ライブラリやブラウザAPIへ接続する場合、useSyncExternalStoreを利用する。
const online = useSyncExternalStore(
subscribe,
() => navigator.onLine,
() => true
);