はじめに
ReactのHooksの登場以降、ステート共有の一般的な方法としてはカスタムフックの方がより推奨されることが多いです。
カスタムフックを使うと、高階関数よりもさらに柔軟で、コンポーネントのネストも深くならないため、よりシンプルにロジックを再利用できます。
以下に、カスタムフックを使って前の例と同じステート共有を実現するサンプルコードを紹介します。
実装
import React, { useState, useCallback } from 'react';
// カウンターのステートと更新ロジックを提供するカスタムフック
const useCounter = () => {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount((prevCount) => prevCount + 1);
}, []);
const decrement = useCallback(() => {
setCount((prevCount) => prevCount - 1);
}, []);
return { count, increment, decrement };
};
// カウンターを表示する子コンポーネント
const CounterDisplay = () => {
const { count } = useCounter();
return <div>現在のカウント: {count}</div>;
};
// カウントを増やすボタンを持つ子コンポーネント
const IncrementButton = () => {
const { increment } = useCounter();
return <button onClick={increment}>増やす</button>;
};
// カウントを減らすボタンを持つ子コンポーネント
const DecrementButton = () => {
const { decrement } = useCounter();
return <button onClick={decrement}>減らす</button>;
};
// 親コンポーネント
const ParentComponent = () => {
return (
<div>
<CounterDisplay />
<IncrementButton />
<DecrementButton />
</div>
);
};
export default ParentComponent;
コードの説明:
-
useCounter
カスタムフック:- この関数は、Reactのフック (
useState
,useCallback
) を内部で使用しています。 -
count
ステートと、それを更新するincrement
およびdecrement
関数を定義しています。 - これらの値と関数をオブジェクトとして返します。
- この関数は、Reactのフック (
-
CounterDisplay
コンポーネント:-
useCounter
フックを呼び出し、返されたcount
を受け取って表示します。
-
-
IncrementButton
コンポーネント:-
useCounter
フックを呼び出し、返されたincrement
関数をボタンのonClick
ハンドラに設定します。
-
-
DecrementButton
コンポーネント:-
useCounter
フックを呼び出し、返されたdecrement
関数をボタンのonClick
ハンドラに設定します。
-
-
ParentComponent
:- 子コンポーネント (
CounterDisplay
,IncrementButton
,DecrementButton
) を直接レンダリングします。
- 子コンポーネント (
カスタムフックの利点:
- シンプルで直感的: コンポーネント内で必要なステートや関数を直接フックから取得するため、propsの受け渡しを意識する必要が減ります。
- 柔軟性: 複数の関連するロジックを1つのフックにまとめることができ、コンポーネントは必要なものだけを選択して利用できます。
- 再利用性の向上: ロジックをコンポーネントのレンダーパスから分離できるため、より純粋なJavaScriptの関数としてテストしやすくなります。
- コンポーネントのネストが浅くなる: 高階関数のようにコンポーネントをラップする必要がないため、コンポーネントツリーがよりシンプルになります。
カスタムフックの注意点:
- カスタムフックは、Reactの関数型コンポーネントまたは別のカスタムフック内でのみ呼び出すことができます(Hookのルール)。
- 同じカスタムフックを複数のコンポーネントで呼び出した場合、それぞれのコンポーネントは独立したステートを持ちます。上記の例では、
CounterDisplay
、IncrementButton
、DecrementButton
はそれぞれuseCounter()
を呼び出しているので、実際にはそれぞれが独立したcount
ステートを持つことになります。
ステートを共有するためのカスタムフック:
もし、複数のコンポーネント間で同じステートを共有したい場合は、カスタムフック内で useState
を一度だけ呼び出し、そのステートと更新関数を複数のコンポーネントに提供する必要があります。これを行う一般的なパターンは、カスタムフック内でコンテキスト (React.createContext
と useContext
) を利用する方法です。
import React, { useState, useCallback, createContext, useContext } from 'react';
// カウンターのコンテキストを作成
const CounterContext = createContext(null);
// カウンターのステートと更新ロジックを提供するプロバイダー
const CounterProvider = ({ children }) => {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount((prevCount) => prevCount + 1);
}, []);
const decrement = useCallback(() => {
setCount((prevCount) => prevCount - 1);
}, []);
return (
<CounterContext.Provider value={{ count, increment, decrement }}>
{children}
</CounterContext.Provider>
);
};
// カウンターのステートと関数を利用するためのカスタムフック
const useCounterContext = () => {
const context = useContext(CounterContext);
if (!context) {
throw new Error("useCounterContext must be used within a CounterProvider");
}
return context;
};
// カウンターを表示する子コンポーネント
const CounterDisplay = () => {
const { count } = useCounterContext();
return <div>現在のカウント: {count}</div>;
};
// カウントを増やすボタンを持つ子コンポーネント
const IncrementButton = () => {
const { increment } = useCounterContext();
return <button onClick={increment}>増やす</button>;
};
// カウントを減らすボタンを持つ子コンポーネント
const DecrementButton = () => {
const { decrement } = useCounterContext();
return <button onClick={decrement}>減らす</button>;
};
// 親コンポーネント
const ParentComponent = () => {
return (
<CounterProvider>
<CounterDisplay />
<IncrementButton />
<DecrementButton />
</CounterProvider>
);
};
export default ParentComponent;
この例では、CounterProvider
が count
ステートと更新関数を管理し、CounterContext.Provider
を通じてその値を子コンポーネントに提供します。useCounterContext
カスタムフックは、このコンテキストの値を利用するための便利なインターフェースを提供します。これにより、CounterDisplay
, IncrementButton
, DecrementButton
は同じ count
ステートを共有し、ボタンの操作によってすべてのコンポーネントの表示が更新されます。
このように、カスタムフックとコンテキストを組み合わせることで、より効率的かつ柔軟にコンポーネント間でステートを共有することができます。大規模なアプリケーションでは、このパターンやより高度なステート管理ライブラリの利用が検討されます。