Closed MofuMofu2 closed 10 months ago
Reactのカスタムフックは、Reactの機能を再利用可能な方法でカプセル化するための強力な機能です。これらは通常のJavaScript関数ですが、フックとしての特別な能力を持っています。Reactのビルトインフック(例えば、useState
やuseEffect
など)の能力を利用して、独自のロジックを封じ込め、コンポーネント間で共有することができます。
関数の定義: カスタムフックは通常のJavaScript関数ですが、名前はuse
で始める必要があります。例えば、useCustomHook
のようにします。
状態と効果の使用: ビルトインのフック(useState
、useEffect
など)を使用して、状態管理や副作用のロジックをカプセル化します。
値の返却: カスタムフックは値(状態、コールバック関数など)を返すことができます。これにより、複数のコンポーネントで同じロジックを再利用できます。
function useCustomHook(initialValue) {
const [value, setValue] = useState(initialValue);
// カスタムロジック
const customLogic = () => {
// 何らかの処理
};
return [value, customLogic];
}
この例では、useCustomHook
は状態value
を管理し、customLogic
という名前の関数を提供します。この関数はコンポーネントで状態を操作するために使用できます。
カスタムフックはReactアプリケーションの柔軟性と再利用性を高める強力なツールです。適切に使用すれば、より効率的で管理しやすいコードを書くことができます。
状態管理や副作用のロジックをカプセル化についてもっと詳しく知りたいです。つまり、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
は、ページのタイトルを設定する副作用をカプセル化します。
カスタムフックを利用することで、Reactコンポーネントの設計がよりモジュラーで、柔軟になります。また、複雑なコンポーネントをより管理しやすい、小さな再利用可能な部分に分割することができます。
ソートロジックやAPIパラメータを参照して適切な値に変換する、などReactに絡まない処理はTypeScriptの関数に持たせることもできると思います。カスタムフックと普通のTypeScript関数を使い分ける方が良いのでしょうか。
はい、カスタムフックと普通のTypeScript(またはJavaScript)関数の使い分けは非常に重要です。これらの使い分けは、関数の責務、依存性、およびReactのライフサイクルとの関連性に基づいて行うべきです。
Reactに依存しないロジック: ソートロジックやAPIパラメータを変換するような、UIやコンポーネントの状態に依存しない純粋なデータ処理は、普通のTypeScript関数で実装するのが適切です。これらの関数は、Reactのコンテキスト外でも再利用可能で、テストが容易です。
汎用的なユーティリティ関数: 日付のフォーマット変換、数値のフォーマット、文字列操作など、アプリケーション全体で汎用的に使用される機能は、普通の関数として定義することが望ましいです。
状態や副作用を含むロジック: useState
、useEffect
、useContext
などのReactフックを使用する場合や、コンポーネントのライフサイクルに関連する処理は、カスタムフック内で扱うのが適切です。
Reactのコンテキストに依存するロジック: コンポーネント間で共有する状態や、特定のコンテキスト(例えばReactのコンテキストAPIを使用している場合)に依存するロジックは、カスタムフックとして実装すると効果的です。
カスタムフックを通したロジックの共有: 複数のコンポーネント間で共有したい状態管理や副作用のロジックは、カスタムフックを使ってモジュール化し、再利用することができます。
これにより、アプリケーションのロジックが適切に整理され、再利用性、テストのしやすさ、および保守性が向上します。また、コンポーネント自体はよりシンプルで宣言的に保たれ、ビジネスロジックとUIロジックの分離が適切に行われます。
handlerやstateなんかはReact依存だからカスタムフックに取り出しておく。 するとフックのテストを書いておけば「値を更新」とかいう共通のロジックはテストを切り出せるってことかな。
逆張りの質問もしてみる。
UIロジックの共通化はメリットがあるのでしょうか。結局表示したいものはそれぞれの場面で違うので、カスタムフックのテストも毎回書き直す必要があるのかなと思いました。
UIロジックの共通化は、確かにそれぞれの場面での表示の違いによって、常に有効とは限らない場合があります。しかし、特定の状況下ではメリットが大きいことも多いです。共通化の主な利点と、その潜在的な課題について考えてみましょう。
コードの重複削減: 同じようなUIロジック(例えば、フォームのバリデーション、モーダルの表示制御など)を複数のコンポーネントで再利用することで、コードの重複を削減できます。
一貫性の維持: 同じUIパターンをアプリケーション全体で使用することで、ユーザーインターフェースの一貫性を保つことができます。これはユーザーエクスペリエンスの向上につながります。
テストの効率化: 共通化されたロジックは一箇所でテストを行うことができるため、テストの効率が向上します。同じロジックを複数の場所でテストする必要がなくなります。
過度な抽象化: UIの要素が異なる場合、共通のロジックが適合しないことがあります。過度に抽象化されたロジックは、特定のUI要素にうまく適用できない可能性があります。
テストの複雑化: 共通化されたUIロジックが多様な用途で使われる場合、そのロジックのテストはより複雑になる可能性があります。異なるコンテキストでの振る舞いをすべてカバーする必要があります。
柔軟性の低下: 特定の場面でのみ必要なカスタマイズが難しくなることがあります。共通化されたロジックはある程度一般化されているため、特殊なケースに対応するための調整が必要になることがあります。
UIロジックの共通化は、アプリケーションの一貫性とメンテナンスの容易さを向上させることができますが、その適用は状況に応じて慎重に行うべきです。
モーダルの表示制御は確かにあるなと思った。
コンテキストが同じならカスタムフックに追い出してテストを書いた方がいいし、そうでないならおとなしくコンポーネントに書いた方がいい。最初はカスタムフックを使わずに実装し、コンテキストが同じ!と気づいたり運用・保守の中で少しずつ構成を変更していくのがいい気がした。
最初からやりすぎると今度は身動きが取れなくなる。
SOLID減速とかそういうやつかな
コンテキストが同じであればUIロジックをカスタムフックに取り出す方がいいですね。最初から何でもカスタムフックにしない方がいい気がします。
その通りです。コンテキストが同じであれば、UIロジックをカスタムフックに取り出すことで多くの利点が得られますが、必ずしも最初からすべてをカスタムフックにする必要はありません。適切なバランスを見極めることが重要です。
結論としては、カスタムフックを利用することは非常に有益ですが、その適用はプロジェクトのニーズや特定の状況に応じて柔軟に行うべきです。開発初期段階では、コンポーネントの構造とロジックが安定するまで待つのも一つの方法です。安定した後に、共通のパターンや再利用可能なロジックをカスタムフックに抽出することが効果的です。
FormRadio.tsxコンポーネントとUseForm.tsというカスタムフックがあるとき、どのようにテストを書くといいでしょうか。コンポーネントとカスタムフックそれぞれのアプローチ例を教えてください。
テストを書く際には、コンポーネントとカスタムフックの両方を個別に考慮する必要があります。以下に、FormRadio.tsx
コンポーネントと UseForm.ts
カスタムフックのテストについてのアプローチ例を示します。
FormRadio.tsx
コンポーネントのテストレンダリングテスト: コンポーネントが正しくレンダリングされることを確認します。これには、必要なプロップスが適切に渡され、期待通りの要素が画面に表示されるかどうかをチェックします。
イベントハンドラのテスト: ラジオボタンのクリックや変更イベントが正しくハンドリングされているかをテストします。これは、ユーザーのインタラクションをシミュレートして確認します。
プロップスに基づく表示のテスト: 異なるプロップスが与えられた場合のコンポーネントの挙動をテストします。例えば、異なるラベルや値が与えられたときの表示の変化などです。
UseForm.ts
カスタムフックのテストフックの機能テスト: フックが提供する各機能(例:状態の更新、バリデーションロジックなど)を個別にテストします。
状態変化のテスト: フックが管理する状態が、入力やイベントに応じて適切に変化するかをテストします。
副作用のテスト: フック内で使用されている 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');
});
});
これらは基本的な例ですが、実際のコードの詳細に応じてテストケースを調整する必要があります。また、各テストケースでカバーするべき具体的な挙動は、実装されたロジックやコンポーネントの要件によって異なります。
フックのテスト方法はまだよくわからず 🤔 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/
カスタムフックを使うとhandlerの更新も共通化できるのが違うかなと思った。 普通の関数とどう使い分ければいいんだろう。
https://react.dev/learn/reusing-logic-with-custom-hooks https://chat.openai.com/share/8c7c0641-11ca-4b65-8bad-67cd7802d6d1