【React】 カスタムフックでロジックを外出し
React Hooksの特徴は、それぞれのhookをコンポーネントの外で記述できる点です。これにより、複雑なロジックをコンポーネント外に押し出し、再利用できる状態にすることで、シンプルなコンポーネントを構築できます。
Hookとは
そもそもReactのhookは、クラスを使わずにReactの機能を書ける機能です。Reactの機能とは、state
やcontext
、effect
等です。
export default function App() { // useStateはhookのひとつ const [count, setCount] = useState(0); // useCallbackはhookのひとつ const incrementCount = useCallback(() => { setCount(count + 1); }, [count]); // useEffectもhookのひとつ useEffect(() => { // ... }, [count]); return ( <> <p>{ count }</p> </> ); }
一瞬ホックと読みたくなりますが、フックです。
カスタムフックでHookをコンポーネントの外に出す
Hookの良いところは、まとまったロジックを外にだすことができる点です。このようにして作った新しいHookをカスタムフックと呼びます。
カスタムフックというと難しく聞こえますが、要はHookを使ったロジックをコンポーネントの外に出しているだけです。
例えば、ボタンを押すと数値が増減する仕組みを考えてみます。普通に作ると、例えば
export default function App() { // ここから const [count, setCount] = useState(5); const increment = () => { setCount(count + 1); }; const decrement = () => { setCount(count - 1); } // ここまでをカスタムフックとして切り出す return ( <> <p>{ count }</p> <button onClick={increment}>+1</button> <button onClick={decrement}>-1</button> </> ); }
のようなものができます。このcount
に関するロジックをカスタムフックとして切り出してみると、例えば
import { useState } from "react" // カスタムフックのコード export const useCounter = (init) => { const [count, setCount] = useState(init); const increment = () => { setCount(count + 1); }; const decrement = () => { setCount(count - 1); } return [count, {increment, decrement}]; };
export default function App() { // カスタムフックの利用 const [count, {increment, decrement}] = useCounter(5); return ( <> <p>{ count }</p> <button onClick={increment}>+1</button> <button onClick={decrement}>-1</button> </> ); }
のように切り出すことができます。これにより、複数箇所に同じようなロジックを書く必要性がなくなります。
Reactのページには、
カスタムフックとは、名前が ”use” で始まり、ほかのフックを呼び出せる JavaScript の関数のことです。https://ja.reactjs.org/docs/hooks-custom.html#extracting-a-custom-hook
と書かれています。そのため、カスタムフック名は上の例のようにuseCounter
など、use
をつけるべきです。
また、Hookが使える場所は、関数コンポーネント内か、カスタムフック内です。
関数のメモ化
useCallback
などを用いたメモ化はReactのパフォーマンスを上げる上で必須です。
ここで、カスタムフックの呼び出される際に、関数などが再度作成されないように、ここでもuseCallback
などを用いていきましょう。
export const useCounter = (init) => { const [count, setCount] = useState(init); const increment = useCallback(() => { setCount(count + 1); }, [count]); const decrement = useCallback(() => { setCount(count - 1); }, [count]); return [count, {increment, decrement}]; };
また、カスタムフック全体を毎回実行するのはコストがかかるので、useMemo
を用いて無駄な呼び出しを削減できる場合もあるなら、それも活用していくと良いです。
カスタムフックの戻り値
カスタムフックを作成する際は、何を返すべきか悩みます。基本的には、
- 特定の
useXx
の形式に合わせる - 全部返す
のどちらかが基本になります。
各種Hookの戻り値の形式
特定のHookに戻り値に合わせるため、それぞれの戻り値を知っておきましょう。
Hook | 戻り値 |
---|---|
useState | {stateの値, {stateの更新関数}} |
useReducer | {stateの値, {stateの更新関数}} |
useEffect | なし |
useCallback | それそのもの (1個) |
useRef | それそのもの (1個) |
ここで、useState
では{state, setState}
のような形式でした。カスタムフックでは更新用関数が複数あることが普通です。これは、どのような更新を行うかをカプセル化するためです。
const [count, setCount] = useState(0);
export const useCounter = (init) => { const [count, setCount] = useState(init); const increment = useCallback(() => { setCount(count + 1); }, [count]); const decrement = useCallback(() => { setCount(count - 1); }, [count]); // 複数個の更新関数を返す return [count, {increment, decrement}]; };
const [count, {increment, decrement}] = useCounter(5);
カスタムフックの利点
上の実際の例を見た上で、どのような利点があるのでしょうか。
- コンポーネントの複雑化を防ぐ
- 機能を再利用できる
- テストが容易になる
2つ目と3つ目は関連していますね。機能を再利用できるということはそれ単体で動かせる状態ということであり、単体で動かせるということはテストが簡単になるということです。
また、ロジックの一部をコンポーネントの外に出すので、コンポーネントの複雑化を防ぐことができます。
まとめ
カスタムフックを用いることで、コンポーネントの肥大化や複雑化を防ぐことができます。また、ロジックの使い回しもしやすくなります。
大規模な開発となると必ず使用するため、小規模な個人制作のものでもどんどん使って練習していくべきです。
