0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

カスタムフックとuseContextでコンポーネント間の状態共有

Posted at

はじめに

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;

コードの説明:

  1. useCounter カスタムフック:

    • この関数は、Reactのフック (useState, useCallback) を内部で使用しています。
    • count ステートと、それを更新する increment および decrement 関数を定義しています。
    • これらの値と関数をオブジェクトとして返します。
  2. CounterDisplay コンポーネント:

    • useCounter フックを呼び出し、返された count を受け取って表示します。
  3. IncrementButton コンポーネント:

    • useCounter フックを呼び出し、返された increment 関数をボタンの onClick ハンドラに設定します。
  4. DecrementButton コンポーネント:

    • useCounter フックを呼び出し、返された decrement 関数をボタンの onClick ハンドラに設定します。
  5. ParentComponent:

    • 子コンポーネント (CounterDisplay, IncrementButton, DecrementButton) を直接レンダリングします。

カスタムフックの利点:

  • シンプルで直感的: コンポーネント内で必要なステートや関数を直接フックから取得するため、propsの受け渡しを意識する必要が減ります。
  • 柔軟性: 複数の関連するロジックを1つのフックにまとめることができ、コンポーネントは必要なものだけを選択して利用できます。
  • 再利用性の向上: ロジックをコンポーネントのレンダーパスから分離できるため、より純粋なJavaScriptの関数としてテストしやすくなります。
  • コンポーネントのネストが浅くなる: 高階関数のようにコンポーネントをラップする必要がないため、コンポーネントツリーがよりシンプルになります。

カスタムフックの注意点:

  • カスタムフックは、Reactの関数型コンポーネントまたは別のカスタムフック内でのみ呼び出すことができます(Hookのルール)。
  • 同じカスタムフックを複数のコンポーネントで呼び出した場合、それぞれのコンポーネントは独立したステートを持ちます。上記の例では、CounterDisplayIncrementButtonDecrementButton はそれぞれ useCounter() を呼び出しているので、実際にはそれぞれが独立した count ステートを持つことになります。

ステートを共有するためのカスタムフック:

もし、複数のコンポーネント間で同じステートを共有したい場合は、カスタムフック内で useState を一度だけ呼び出し、そのステートと更新関数を複数のコンポーネントに提供する必要があります。これを行う一般的なパターンは、カスタムフック内でコンテキスト (React.createContextuseContext) を利用する方法です。

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;

この例では、CounterProvidercount ステートと更新関数を管理し、CounterContext.Provider を通じてその値を子コンポーネントに提供します。useCounterContext カスタムフックは、このコンテキストの値を利用するための便利なインターフェースを提供します。これにより、CounterDisplay, IncrementButton, DecrementButton は同じ count ステートを共有し、ボタンの操作によってすべてのコンポーネントの表示が更新されます。

このように、カスタムフックとコンテキストを組み合わせることで、より効率的かつ柔軟にコンポーネント間でステートを共有することができます。大規模なアプリケーションでは、このパターンやより高度なステート管理ライブラリの利用が検討されます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?