useReducerのdocsを読む
これです
useReducer is a React Hook that lets you add a reducer to your component.
わりかし推しAPIかもしれん。というかReducerという抽象が好きなのか。
Reference
import { useReducer } from 'react';
function reducer(state, action) {
// ...
}
function MyComponent() {
const [state, dispatch] = useReducer(reducer, { age: 42 });
// ...
optional init: The initializer function that should return the initial state. If it’s not specified, the initial state is set to initialArg. Otherwise, the initial state is set to the result of calling init(initialArg).
initを渡すとinit(initialArg)で初期値を計算する
Caveats はuseStateと大体同じ
Usage
useStateとuseReducerの使い分けはここに書いてあるらしい。useReducerはAPI docsよりもLearnの方が面白いかも。
Avoiding recreating the initial state
こうじゃなくて
function createInitialState(username) {
// ...
}
function TodoList({ username }) {
const [state, dispatch] = useReducer(reducer, createInitialState(username));
// ...
こう!
function createInitialState(username) {
// ...
}
function TodoList({ username }) {
const [state, dispatch] = useReducer(reducer, username, createInitialState);
// ...
上だと毎回呼ばれてしまう。init時にしか必要ないのに。
Troubleshootingも大体useStateと一緒で、あとはTS使っとけば悩まないだろうなっていうのばかり。
useReducerが気持ちいいの、集合全体を掌握できている感なんだろうな。TSを併用して威力を発揮する。
useStateと被りが多くてあまりにも読み応えがないので、関連Learnもここで読んじゃおう。
As your components grow in complexity, it can get harder to see at a glance all the different ways in which a component’s state gets updated. For example, the TaskApp component below holds an array of tasks in state and uses three different event handlers to add, remove, and edit tasks:
イベントハンドラが増えてきたらactionへと抽象化しようっていう導入になっている。
それ使ってカスタムフック書いたらどうせactionの数だけイベントハンドラ書くのだけれども、reducerに押し込んであるだけマシって感じで使うよね。
Reducers are a different way to handle state. You can migrate from useState to useReducer in three steps:
- Move from setting state to dispatching actions.
- Write a reducer function.
- Use the reducer from your component.
useStateからuseReducerへの移行ガイドとして書かれている。そういえばそんなタイトルだった。
先生がどんな説明するか気になるという理由で細かく読む。
Step 1: Move from setting state to dispatching actions
Managing state with reducers is slightly different from directly setting state. Instead of telling React “what to do” by setting state, you specify “what the user just did” by dispatching “actions” from your event handlers. (The state update logic will live elsewhere!) So instead of “setting tasks” via an event handler, you’re dispatching an “added/changed/deleted a task” action. This is more descriptive of the user’s intent.
setStateの頃は「(システムが)何をするか」を記述していたが、dispatchだと「ユーザーが何をしたか」を記述することになる。
たしかにこれ、useReducerが好きな理由の1つだわ。言語化うま。
Choose a name that says what happened!
typeの命名も当然こうなる
Step 2: Write a reducer function
至極簡潔な説明
- Declare the current state (tasks) as the first argument.
- Declare the action object as the second argument.
- Return the next state from the reducer (which React will set the state to).
Because the reducer function takes state (tasks) as an argument, you can declare it outside of your component. This decreases the indentation level and can make your code easier to read.
そうだね、カスタムフックと同じでComponentがStateの計算から開放されるのがいいところだよね。This decreases the indentation level
はそう思うけれど直接言及するのは意外だったな。
We recommend wrapping each case block into the { and } curly braces so that variables declared inside of different cases don’t clash with each other. Also, a case should usually end with a return. If you forget to return, the code will “fall through” to the next case, which can lead to mistakes!
おーこれもなんか意外だな。caseは{ }
で囲もうと。普段やってないな。
Why are reducers called this way?
イメージとしてはこうだよねという。
let initialState = [];
let actions = [
{type: 'added', id: 1, text: 'Visit Kafka Museum'},
{type: 'added', id: 2, text: 'Watch a puppet show'},
{type: 'deleted', id: 1},
{type: 'added', id: 3, text: 'Lennon Wall pic'},
];
let finalState = actions.reduce(tasksReducer, initialState);
畳み込みのイメージってむずいよね。なんとなくあのグラフは思い浮かぶけど、実際読むとき困惑することよくある。
Step 3: Use the reducer from your component
あとはuseReducerに詰め込むだけ。
ここ真理ですね
Component logic can be easier to read when you separate concerns like this. Now the event handlers only specify what happened by dispatching actions, and the reducer function determines how the state updates in response to them.
Comparing useState and useReducer
- コードサイズ:一般的に、useStateを使えば、最初に書くコードは少なくて済む。useReducerでは、リデューサ関数とディスパッチアクションの両方を書く必要があります。しかし、多くのイベントハンドラが同じような方法でステートを変更する場合、useReducerはコードを削減するのに役立ちます。
- 可読性:ステートの更新が単純な場合、useStateは非常に読みやすい。これが複雑になると、コンポーネントのコードが肥大化し、スキャンが困難になります。このような場合、useReducerを使用すると、更新ロジックの方法とイベントハンドラの何が起こったかをきれいに分けることができます。
- デバッグ:useStateでバグが発生した場合、どこで状態が誤って設定されたのか、なぜそうなったのかを特定するのは困難です。useReducerを使えば、コンソールログをReducerに追加することで、ステートの更新をすべて確認することができます。それぞれのアクションが正しければ、間違いはリデューサーのロジック自体にあることがわかります。しかし、useStateよりも多くのコードを踏む必要があります。
- テスト:リデューサーはコンポーネントに依存しない純粋な関数です。つまり、個別にエクスポートしてテストすることができます。一般的には、より現実的な環境でコンポーネントをテストするのがベストですが、複雑な状態更新ロジックでは、特定の初期状態とアクションに対して特定の状態を返すリデューサをアサートするのが便利です。
- 個人的な好み:リデューサーが好きな人もいれば、そうでない人もいます。それはそれでいいのです。好みの問題です。useStateとuseReducerはいつでも相互に変換できます!
Stateが直和型で表現できるときにはuseReducerを積極的に使いたいと思っている
Writing reducers well
Each action describes a single user interaction, even if that leads to multiple changes in the data. For example, if a user presses “Reset” on a form with five fields managed by a reducer, it makes more sense to dispatch one reset_form action rather than five separate set_field actions.
システムの関心ではなく、ユーザーの関心をもとにactionを定義する
Writing concise reducers with Immer
公式ドキュメントで紹介するほどか?
Try out some challenges
こういうプリミティブな理解を促進するワークアウトいいよね
import { useState } from 'react';
export function useReducer(reducer, initialState) {
const [state, setState] = useState(initialState);
const dispatch = (action) => setState(prev => reducer(prev, action))
return [state, dispatch];
}
読んでみて
このページに限ったことではないけれど、ドキュメントが丁寧なので「開発者の共通理解」として共有するのには大変優れているなと思いました。