useStateは便利ですが、増えてくると散らかります。ことりずかんの一覧ページを見てみましょう。
const [keyword, setKeyword] = useState('')
const [onlyFavorites, setOnlyFavorites] = useState(false)いまは2つなので問題ありません。ここに実務でよくある要求が来たとします。
- 並べ替え(名前順・レア度順)を足したい
- ページ送りを足したい
- 検索語を変えたら、ページを1に戻したい
最後の1つが効いてきます。state同士が連動し始めたのです。
function handleKeywordChange(value) {
setKeyword(value)
setPage(1) // ← 書き忘れると、2ページ目のまま検索されてバグる
}この「一緒に変えなければいけない」が増えるほど、書き忘れが起きます。
useReducer——更新のしかたを1か所に集める
useReducerは、stateと「どう変わるか」をひとまとめにする道具です。
import { useReducer } from 'react'
const initialState = {
keyword: '',
onlyFavorites: false,
sort: 'name',
page: 1,
}
function reducer(state, action) {
switch (action.type) {
case 'search':
// 検索したらページは必ず1に戻す——このルールがここに閉じこめられる
return { ...state, keyword: action.value, page: 1 }
case 'toggleOnlyFavorites':
return { ...state, onlyFavorites: !state.onlyFavorites, page: 1 }
case 'changeSort':
return { ...state, sort: action.value, page: 1 }
case 'changePage':
return { ...state, page: action.value }
default:
return state
}
}
function BirdListPage() {
const [state, dispatch] = useReducer(reducer, initialState)
…
}使う側はこうなります。
<input
value={state.keyword}
onChange={(event) => dispatch({ type: 'search', value: event.target.value })}
/>「検索した」と知らせるだけで、「ページを1に戻す」は書きません。そのルールはreducerが知っているからです。
useStateのままでよい場面
「増えたら全部reducerにする」ではありません。線引きははっきりしています。
| 状況 | どちらを使うか |
|---|---|
| stateが2〜3個で、それぞれ独立している | useState |
| 入力欄の値のように、単純に置きかえるだけ | useState |
| 複数のstateが連動して変わる | useReducer |
| 更新のしかたが何通りもある(追加・削除・並べ替え・リセット) | useReducer |
| 同じ更新を、離れた場所から何度も呼ぶ | useReducer |
reducerの中で守ること
reducerには決まりが1つあります。元のstateを書きかえないことです。
// ✕ 元を書きかえている
case 'search':
state.keyword = action.value
return state
// ○ 新しいオブジェクトを返す
case 'search':
return { ...state, keyword: action.value, page: 1 }stateの回で学んだ「直接書きかえない」が、そのまま効いています。reducerは常に新しいオブジェクトを返す——これだけ守れば大丈夫です。
Contextと組み合わせる
useReducerはdispatchを1つ配るだけで済むので、Contextと組み合わせるのが定番です。
<FilterContext value={{ state, dispatch }}>値をいくつも配る代わりに、stateとdispatchの2つだけを配ればよくなります。規模が大きくなったアプリで、この形はよく見かけます。
useReducerと組み合わせるのは、その自然な発展形です公式ドキュメントも見てみる
ja.react.dev外部サイトstate ロジックをリデューサに抽出する – React公式の「stateロジックをリデューサに抽出する」。useStateからuseReducerへ書きかえる手順が、3ステップで整理されていますこのレッスンのまとめ
useStateは連動するstateが増えると書き忘れが起きるuseReducerはstateと更新のしかたを1か所にまとめる道具- 登場人物はstate・action・reducer。reducerはただの関数でテストしやすい
- 呼ぶ側は「何が起きたか」を
dispatchするだけ。更新の手順を知らなくてよい - 2〜3個の独立したstateなら
useStateのままでよい - reducerでも元のstateを書きかえない。
defaultでreturn stateを忘れない - 速くはならない。ただし
dispatchは変わらないのでmemoした子に渡しやすい
次は、Reactに任せない部分——refでDOMを直接触る話です。