https://qiita.com/uhyo/items/cea1bd157453a85feebf
React Hooksの正式リリース(2019年2月)からそろそろ一年が経とうとしています。Hooksの登場によってReactのコンポーネントは関数コンポーネントが一気に主流になり、クラスコンポーネントが新規に作られる機会は激減しました。
また、React 17.x系ではConcurrent Modeの導入とともにさらに2種類の新フックが追加される見込みであり、いよいよ関数コンポーネントの能力がクラスコンポーネントを真に上回る時代が来ることになります。
この記事では、フックの一種であるuseReducerに焦点を当てて、どのようなときにuseReducer
が適しているのかを説明します。究極的には、useReducerによって達成できるパフォーマンス改善があり、ときにはそれがコンポーネント設計にまで影響を与えることを指摘します。
useStateの影に隠れたり、なぜかReduxと比較されたりといまいちぱっとしないuseReducerですが、この記事でその真の魅力を知っていただければ幸いです。
useReducer
は、ステートに依存するロジックをステートに非依存な関数オブジェクト(dispatch
)で表現することができる点が本質である。React.memo
によるパフォーマンス改善につながる。useReducer
を活かすには、ステートを一つにまとめることで、ロジックをなるべくreducerに詰め込む。useReducer
とはまずは、初心者の方向けにuseReducer
の動作を説明します。すでに知っているという方は次の節まで飛ばしても構いません。
useReducer
はフックの一種であり、関数コンポーネントのステートを宣言する能力を持ちます。ステートの宣言はuseState
とuseReducer
の2種類の方法がありますが、useReducer
は複雑なロジックが絡んだステートを宣言するのに適しています。
useReducer
は以下のように使います。こちらが用意するのはreducer
とinitialState
の2つです。reducer
は「現在のステート」と「アクション」を受け取って「新しいステート」を返す関数であり、initialState
はステートの初期値です。
useReducer
の返り値は2つで、currentState
はステートの現在の値、dispatch
はアクションを発火する関数です。dispatch
にアクションを渡すと、内部でreducer
が呼び出されて新しいステートが計算され、コンポーネントが再レンダリングされて新しいステートが反映されます。
一応簡単な例を示しておきます。まずはreducer
の例です。分かりやすさのためにTypeScriptを用いています。
type State = {
count: number
};
type Action = {
type: "increment" | "decrement";
};
const reducer = (state: State, action: Action): State => {
if (action.type === "increment") {
return {
count: state.count + 1
};
} else {
return {
count: state.count - 1
}
}
};
ここではアクションは{ type: "increment" }
または{ type: "decrement" }
です。見て分かる通り、これはそれぞれ「カウンタを1増やす」操作と「カウンタを1減らす」操作に相当します。このreducerによって管理されるState
は{ count: number }
です。つまり、カウンタの数値をひとつ持っているだけのオブジェクトです。この場合type State = number
でも別に構いませんが、今後の拡張性を考えてこの定義にしています。
これはconst [state, dispatch] = useReducer(reducer, { count: 0 })
のように使用します。このdispatch
を用いて、dispatch({ type: "increment" })
とすればステートが変化してカウンタの値が1増えるでしょう。
これがuseReducer
の使い方です。useReducer
は、ステートの種類が増えたりロジックが増えたりしてもその操作の窓口がdispatch
という一点に集約されている点がポイントです。子コンポーネントが何かしらのロジックを発火したいときはdispatch
をpropsで渡すだけでいいし、コンポーネントツリーが大きい場合はコンテキストを用いて子に伝えるのも有効でしょう。
useReducer
がパフォーマンス改善につながる例