問題
React の Reducer がなぜか 2回 Dispatch される。
同じ処理が 2回 実行されるため、予期しない結果となる。
原因
Reducer が純粋関数でないため。
正直、この原因を知ったところで根本解決は難しい。
以下に回避策をまとめる。
回避策
以下の3つがある模様。
- Strict Mode を解除する
- reducer を
useCallback
で囲む - reducer をメイン関数の外に出す?
今回は個人開発ということもあり、内容を確認した上で 1 を選択しました
① Strict Mode の解除
Strict Mode の解除に関しては、内容を理解した上で自己責任で行いましょう。
特に会社のプロダクトの場合は本当に問題ないか、要検討です。
詳細は Strict Mode に書いてあります。
やることは <React.StrictMode>
を削除するだけです。
修正後
ReactDOM.render(
<MyComponent>,
document.getElementById("root")
)
修正前
ReactDOM.render(
<React.StrictMode>
<MyComponent>
</React.StrictMode>,
document.getElementById("root")
)
② useCallback でラップ
こちらは試したが、自分の環境では問題を解決できなかった。
一応記載。
useReducer(useCallback(reducer), initState)
② reducer をメイン関数の外に出す
以下で言及されていましたが、正直意図がわからず。
コンポーネント関数の外に出せという意味でしょうか?
自分の場合は最初からコンポーネント関数の外に出していたため、解決策には成りえませんでした。
背景など
日本語で調べても情報が全然出てこない
日本語で検索生ても全然情報が出てきません。
英語で検索すると次の Issue がヒットしました。
解決に至った Issue
useReducer dispatch calls reduce twice #16295
Issue の議論の要約
上記の Issue で議論を要約するとこんな感じになります。
- 原因は Reducer の関数が純粋でないためです
- ある特定の条件下で Reducer が 2回 Dispatch されるのは 仕様 です
- Reducer は純粋関数であるべきです
- 純粋関数であれば、2回 呼ばれても問題ありません
- この情報はドキュメントに書くべきでは?
React はドキュメントの内容が少ない
これは個人的に感じているだけです。
React のドキュメントは情報量が少ないように思います。
例えば useReducer を例に取ってみても、
- 引数の型は何か?
- 戻り地の型は何か?
- それらの型のフィールド・メソッドは何か?
- どの条件でコンポーネントは再レンダリングされるのか?
- 子コンポーネントまで再レンダリングされるのか?
などが書かれていません。
API Reference と言っていますが、個人的にはもっと格式張った内容を把握したいです。
ここまで書いていて「ソースコードを読め」という天啓が降って気がしましたが、辛いですね。
自分もまだまだですね、、、。
自分が書いた Reducer は純粋関数なはずであるが、、、?
そもそも純粋関数とは、「引数が同じであればm常に結果を返す関数」のことです。
そのオブジェクトの内部状態に結果が影響されません。
内部状態と行っているのは、フィールドやメンバーのことです。
受け取った値が同じであれば、常に同じ値を返します。
このような特徴を「副作用がない」とも表現します。
自分が書いた Reducer は引数のオブジェクトのメソッドを手続き的に呼び出すだけなんので、純粋関数のはずです。
そもそも今回自分が遭遇した減少としては、「同じ処理が2回呼ばれた結果、単純に 2回 処理されてしまった」というものです。
Reducer で管理している State のあるフラグを切り替える処理だったのですが、2回 呼ばれた結果元に戻ってしまいました。
true の場合は false を返し、false の場合は true を返す、副作用はないはずです。
何か見落としがあるのかもしれませんが、あまり今回の現象の全貌を理解できた気がしません。
最後に
個人開発を頑張っているので、ぜひ twitter 見てみてください。
毎日 1 コミットを実践中です
フォローされると泣いて喜びます😭