MofuMofu2 / react-sandbox

React.js Sandbox
1 stars 0 forks source link

7-8 フックの自作 #73

Closed MofuMofu2 closed 10 months ago

MofuMofu2 commented 1 year ago

カスタムフックを使うとhandlerの更新も共通化できるのが違うかなと思った。 普通の関数とどう使い分ければいいんだろう。

https://react.dev/learn/reusing-logic-with-custom-hooks https://chat.openai.com/share/8c7c0641-11ca-4b65-8bad-67cd7802d6d1

MofuMofu2 commented 10 months ago
MofuMofu2 commented 10 months ago

Reactのカスタムフックは、Reactの機能を再利用可能な方法でカプセル化するための強力な機能です。これらは通常のJavaScript関数ですが、フックとしての特別な能力を持っています。Reactのビルトインフック(例えば、useStateuseEffectなど)の能力を利用して、独自のロジックを封じ込め、コンポーネント間で共有することができます。

カスタムフックの作成方法

  1. 関数の定義: カスタムフックは通常のJavaScript関数ですが、名前はuseで始める必要があります。例えば、useCustomHookのようにします。

  2. 状態と効果の使用: ビルトインのフック(useStateuseEffectなど)を使用して、状態管理や副作用のロジックをカプセル化します。

  3. 値の返却: カスタムフックは値(状態、コールバック関数など)を返すことができます。これにより、複数のコンポーネントで同じロジックを再利用できます。

カスタムフックの例

function useCustomHook(initialValue) {
  const [value, setValue] = useState(initialValue);

  // カスタムロジック
  const customLogic = () => {
    // 何らかの処理
  };

  return [value, customLogic];
}

この例では、useCustomHookは状態valueを管理し、customLogicという名前の関数を提供します。この関数はコンポーネントで状態を操作するために使用できます。

カスタムフックの利点

カスタムフックはReactアプリケーションの柔軟性と再利用性を高める強力なツールです。適切に使用すれば、より効率的で管理しやすいコードを書くことができます。

MofuMofu2 commented 10 months ago

状態管理や副作用のロジックをカプセル化についてもっと詳しく知りたいです。つまり、Reactコンポーネントから隔離した状態でロジックを独立させることができるという理解であってますか

はい、おっしゃる通りです。Reactのカスタムフックにより、コンポーネントからロジック(特に状態管理や副作用に関連するロジック)を隔離し、独立させることが可能です。これにより、コンポーネント自体はより宣言的で、理解しやすくなります。また、ロジックは再利用可能な形で整理されます。

状態管理のカプセル化

カスタムフック内でuseStateを使用することにより、特定の状態管理ロジックを隔離します。これにより、同じ状態管理のパターンを複数のコンポーネントで再利用できます。

例:

function useCounter(initialCount = 0) {
  const [count, setCount] = useState(initialCount);

  const increment = () => setCount(prevCount => prevCount + 1);
  const decrement = () => setCount(prevCount => prevCount - 1);

  return { count, increment, decrement };
}

このカスタムフックuseCounterはカウンターのロジックをカプセル化し、どのコンポーネントでも使用できるようにします。

副作用のカプセル化

useEffectを使用することで、副作用(データのフェッチ、購読、手動でのDOMの更新など)をカスタムフック内に隔離できます。これにより、副作用のロジックがコンポーネント間で共有可能になります。

例:

function useDocumentTitle(title) {
  useEffect(() => {
    document.title = title;
  }, [title]);
}

このカスタムフックuseDocumentTitleは、ページのタイトルを設定する副作用をカプセル化します。

隔離された状態の利点

  1. 再利用性: 同じロジックを異なるコンポーネントで簡単に共有できます。
  2. 清潔なコンポーネント: コンポーネントはUIとそのイベントハンドラに集中でき、状態や副作用のロジックは隔離されます。
  3. 保守性の向上: 隔離されたロジックは理解しやすく、変更やテストが容易になります。

カスタムフックを利用することで、Reactコンポーネントの設計がよりモジュラーで、柔軟になります。また、複雑なコンポーネントをより管理しやすい、小さな再利用可能な部分に分割することができます。

MofuMofu2 commented 10 months ago

ソートロジックやAPIパラメータを参照して適切な値に変換する、などReactに絡まない処理はTypeScriptの関数に持たせることもできると思います。カスタムフックと普通のTypeScript関数を使い分ける方が良いのでしょうか。

はい、カスタムフックと普通のTypeScript(またはJavaScript)関数の使い分けは非常に重要です。これらの使い分けは、関数の責務、依存性、およびReactのライフサイクルとの関連性に基づいて行うべきです。

TypeScriptの関数の使用ケース

カスタムフックの使用ケース

まとめ

これにより、アプリケーションのロジックが適切に整理され、再利用性、テストのしやすさ、および保守性が向上します。また、コンポーネント自体はよりシンプルで宣言的に保たれ、ビジネスロジックとUIロジックの分離が適切に行われます。

MofuMofu2 commented 10 months ago

handlerやstateなんかはReact依存だからカスタムフックに取り出しておく。 するとフックのテストを書いておけば「値を更新」とかいう共通のロジックはテストを切り出せるってことかな。

MofuMofu2 commented 10 months ago

逆張りの質問もしてみる。

UIロジックの共通化はメリットがあるのでしょうか。結局表示したいものはそれぞれの場面で違うので、カスタムフックのテストも毎回書き直す必要があるのかなと思いました。

UIロジックの共通化は、確かにそれぞれの場面での表示の違いによって、常に有効とは限らない場合があります。しかし、特定の状況下ではメリットが大きいことも多いです。共通化の主な利点と、その潜在的な課題について考えてみましょう。

UIロジックの共通化のメリット

  1. コードの重複削減: 同じようなUIロジック(例えば、フォームのバリデーション、モーダルの表示制御など)を複数のコンポーネントで再利用することで、コードの重複を削減できます。

  2. 一貫性の維持: 同じUIパターンをアプリケーション全体で使用することで、ユーザーインターフェースの一貫性を保つことができます。これはユーザーエクスペリエンスの向上につながります。

  3. テストの効率化: 共通化されたロジックは一箇所でテストを行うことができるため、テストの効率が向上します。同じロジックを複数の場所でテストする必要がなくなります。

UIロジックの共通化の課題

  1. 過度な抽象化: UIの要素が異なる場合、共通のロジックが適合しないことがあります。過度に抽象化されたロジックは、特定のUI要素にうまく適用できない可能性があります。

  2. テストの複雑化: 共通化されたUIロジックが多様な用途で使われる場合、そのロジックのテストはより複雑になる可能性があります。異なるコンテキストでの振る舞いをすべてカバーする必要があります。

  3. 柔軟性の低下: 特定の場面でのみ必要なカスタマイズが難しくなることがあります。共通化されたロジックはある程度一般化されているため、特殊なケースに対応するための調整が必要になることがあります。

結論

UIロジックの共通化は、アプリケーションの一貫性とメンテナンスの容易さを向上させることができますが、その適用は状況に応じて慎重に行うべきです。

MofuMofu2 commented 10 months ago

モーダルの表示制御は確かにあるなと思った。

MofuMofu2 commented 10 months ago

コンテキストが同じならカスタムフックに追い出してテストを書いた方がいいし、そうでないならおとなしくコンポーネントに書いた方がいい。最初はカスタムフックを使わずに実装し、コンテキストが同じ!と気づいたり運用・保守の中で少しずつ構成を変更していくのがいい気がした。

最初からやりすぎると今度は身動きが取れなくなる。

MofuMofu2 commented 10 months ago

SOLID減速とかそういうやつかな

コンテキストが同じであればUIロジックをカスタムフックに取り出す方がいいですね。最初から何でもカスタムフックにしない方がいい気がします。

その通りです。コンテキストが同じであれば、UIロジックをカスタムフックに取り出すことで多くの利点が得られますが、必ずしも最初からすべてをカスタムフックにする必要はありません。適切なバランスを見極めることが重要です。

カスタムフックにUIロジックを取り出すメリット

  1. 再利用性の向上: 似たような振る舞いをするコンポーネント間でロジックを共有できる。
  2. 可読性と保守性の向上: コンポーネントからビジネスロジックを分離することで、コードが読みやすく、保守しやすくなる。
  3. テストの容易さ: 独立したロジックは、コンポーネントから分離してテストしやすくなる。

カスタムフックへの取り出しを検討するタイミング

注意点

結論としては、カスタムフックを利用することは非常に有益ですが、その適用はプロジェクトのニーズや特定の状況に応じて柔軟に行うべきです。開発初期段階では、コンポーネントの構造とロジックが安定するまで待つのも一つの方法です。安定した後に、共通のパターンや再利用可能なロジックをカスタムフックに抽出することが効果的です。

MofuMofu2 commented 10 months ago

FormRadio.tsxコンポーネントとUseForm.tsというカスタムフックがあるとき、どのようにテストを書くといいでしょうか。コンポーネントとカスタムフックそれぞれのアプローチ例を教えてください。

テストを書く際には、コンポーネントとカスタムフックの両方を個別に考慮する必要があります。以下に、FormRadio.tsx コンポーネントと UseForm.ts カスタムフックのテストについてのアプローチ例を示します。

FormRadio.tsx コンポーネントのテスト

アプローチ

  1. レンダリングテスト: コンポーネントが正しくレンダリングされることを確認します。これには、必要なプロップスが適切に渡され、期待通りの要素が画面に表示されるかどうかをチェックします。

  2. イベントハンドラのテスト: ラジオボタンのクリックや変更イベントが正しくハンドリングされているかをテストします。これは、ユーザーのインタラクションをシミュレートして確認します。

  3. プロップスに基づく表示のテスト: 異なるプロップスが与えられた場合のコンポーネントの挙動をテストします。例えば、異なるラベルや値が与えられたときの表示の変化などです。

テストツール

UseForm.ts カスタムフックのテスト

アプローチ

  1. フックの機能テスト: フックが提供する各機能(例:状態の更新、バリデーションロジックなど)を個別にテストします。

  2. 状態変化のテスト: フックが管理する状態が、入力やイベントに応じて適切に変化するかをテストします。

  3. 副作用のテスト: フック内で使用されている useEffect やその他の副作用が期待通りに動作するかをテストします。

テストツール

テストコードの例

コンポーネントのテスト(FormRadio.tsx):

import { render, fireEvent } from '@testing-library/react';
import FormRadio from './FormRadio';

describe('FormRadio Component', () => {
  it('renders correctly', () => {
    const { getByLabelText } = render(<FormRadio label="Test Radio" />);
    expect(getByLabelText("Test Radio")).toBeInTheDocument();
  });

  it('handles change event', () => {
    const handleChange = jest.fn();
    const { getByLabelText } = render(<FormRadio label="Test Radio" onChange={handleChange} />);
    fireEvent.click(getByLabelText("Test Radio"));
    expect(handleChange).toHaveBeenCalled();
  });
});

カスタムフックのテスト(UseForm.ts):

import { renderHook, act } from '@testing-library/react-hooks';
import useForm from './UseForm';

describe('useForm Hook', () => {
  it('handles state correctly', () => {
    const { result } = renderHook(() => useForm());

    act(() => {
      result.current.updateValue('test');
    });

    expect(result.current.value).toBe('test');
  });
});

これらは基本的な例ですが、実際のコードの詳細に応じてテストケースを調整する必要があります。また、各テストケースでカバーするべき具体的な挙動は、実装されたロジックやコンポーネントの要件によって異なります。

MofuMofu2 commented 10 months ago

フックのテスト方法はまだよくわからず 🤔 GPTの例だとReact17までの書き方になっていて警告が出る。Renderの方法が古いということ。これはライブラリ側で対応できないとだめな気もするが

https://www.builder.io/blog/test-custom-hooks-react-testing-library https://zenn.dev/dove/articles/e00696be89a022 https://dev.to/ms314006/react-write-a-unit-test-for-custom-hooks-in-ten-minutes-497l https://codesandbox.io/s/testing-custom-hook-demo-ko6bq https://www.rumblefish.dev/blog/post/how-to-test-custom-react-hooks/