やったこと
前回、Memo化をまとめる中、useCallbackを扱った
その際、カスタムフックの話が出てきたので、今回はCustom Hookについて触れたいと思う。
公式doc 独自フックの作成
自分独自のフックを作成することで、コンポーネントからロジックを抽出して再利用可能な関数を作ることが可能になる。
カスタムフックとは、名前が ”use” で始まり、ほかのフックを呼び出せる JavaScript の関数のこと
- 同じフックを使うコンポーネントは state を共有する?
- いいえ。
- カスタムフックは state を使うロジック(データの購読を登録したり現在の値を覚えておいたり)を共有するためのものですが、 カスタムフックを使う場所ごとで、内部の state や副作用は完全に分離しています。
カスタムフックを使うメリット
- 複数のReact hooksをまとめることができる
- View(コンポーネント)とロジックが分離できる
カスタムフックの設計や実装する上で気をつけること
1. 戻り値にルールを設ける
一番個人によってバラバラになりやすいのが、戻り値であり、プロジェクトごとにルールを設けた方がいい。
パターン1. React hooksのスタイルに合わせる
React hooksにスタイルを合わせるメリット
- 戻り値だけで、どのようなフックであるかある程度予測がつく
カスタムフックの処理自体にも統制をとることができる - 戻り値が肥大化した際に、処理を分割すべきか見直すことができるため
パターン2: オブジェクトですべて返す
返したいものを全てオブジェクトに詰め込んで返すパターン
オブジェクトで返すのメリット
- 返却値を増やしたい時に変更がいらない
- コンポーネントで呼び出す際に、返却値の命名が変更されることがない
- テスト時に値を取りやすい (result.current.〇〇でとれる)
2. メモ化を意識する
関数を返すカスタムフックに関してはすべてuseCallbackで囲ってあげるのがよい
useCallbackはとにかく使え! 特にカスタムフックでは
(例)
const App: React.VFC = () => {
const handleClick = useCallback((e: React.MouseEvent) => {
console.log("clicked!");
}, []);
return (
<button onClick={handleClick}>button</button>
);
};
useCallbackは、初回の呼び出し(Appの初回のレンダリング)では渡された関数をそのまま返す。
Appが再レンダリングされたとき、useCallbackの返り値としては初回レンダリング時のときの関数オブジェクトが再利用される。
(useCallbackに渡された関数オブジェクトは今回は捨てられます)
つまり、handleClickは初回のレンダリング時も2回目のレンダリング時も同じ(===の意味で等しい)関数オブジェクトである。
カスタムフックと責務の分離
カスタムフックを作る理由は、普通の関数を作る理由と全く同じであり、すなわち責務の分離とかカプセル化
一度カスタムフックとして分離された以上、インターフェースの内側のことはカスタムフック内で完結すべきです。
カスタムフックを使う側はカスタムフックの内側のことを知るべきではなく、その逆も然りです。
つまり、useToggleが返すtoggle関数が毎回変わる(=使う側に再レンダリングを強制する)のか、
それともuseCallbackで囲まれていて基本的に変わらない(=使う側は再レンダリングを抑制できる)のかは、
useToggleの仕様の一部としてuseToggle側が決めること
もしも「useToggleの返り値が毎回変わっていてSuperHeavyButtonが再レンダリングされてしまい困るからuseToggleにuseCallbackを追加した」
というようなことが起こった場合は、それはuseToggleを使う側の都合を鑑みてuseToggleを仕様変更したということになる
つまり、useToggleをコンポーネントロジックから分離して再利用可能にしたつもりが、結局使う側に振り回されてしまい再利用可能になっていなかったということ
カスタムフックのインターフェース上の意味を考えてみても、useCallbackを使う方が妥当である場合が多い
例えば、Reactに組み込みのuseStateが返す関数(ステート更新関数)は毎回同じ関数であることが保証されている
そもそもReactの世界では、「値が違う」(===ではない)ことが色々な処理のトリガーになります。
React.memoもそうですし、useStateやコンテキストなども“違う”値が入ることが再レンダリングを引き起こします。
ですから、違わないものは違わないと明確にする(===になるようにする)ことには、単なるパフォーマンス最適化だけではなくロジック上の意味が付与される
実際に使ってみる。
過去に作ったアプリで長かった関数を一つカスタムフック化してみた!
やってみるとすごくスッキリした
次はコンテクストについてまとめる予定