🐒

【React】カスタムフックは積極的に使おう!

2022/05/18に公開

こんにちは、@Yuki_TUです。
みなさんは、カスタムフックは積極的に使っていますでしょか?
ついつい、useEffect, useStateなどを直で描きがちではないでしょうか?
私も最初の方はそうでした。。。

今回、カスタムフックを使った事例を紹介できたらと思います。

カスタムフックを使わない場合

メールアドレスとパスワードでサインインする場合を考えます。
この時、各入力値をステートとして、扱います。

通常に考えると以下の感じで、メールアドレスと、パスワードそれぞれにuseStateでステート用意するという考えが、浮かぶのではないでしょうか?
今回は少ないコード量なので、これでも大きな問題はないのですが、サインアップ画面などになると、コード量が増えるのは想像できますね。。(メアド、住所、電話番号 、ユーザID、パスワード、パスワード確認などなど・・・)

SignIn.tsx
import React, { useCallback, useState } from 'react';
import { TextInput } from '../../uiParts/TextInput';
import { PrimaryButton } from '../../uiParts/PrimaryButton';
import { TextLink } from '../../uiParts/TextLink';

/**
 * サインイン画面のコンポーネント
 * @return サインアップコンポーネント
 */
function SignIn() {
  // ユーザ登録に関する情報ステータス
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  /** 入力メールアドレスの更新 */
  const inputEmail = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      setEmail(e.target.value);
    },
    [setEmail]
  );
  
  /** 入力パスワードの更新 */
  const inputPassword = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      setPassword(e.target.value);
    },
    [setPassword]
  );

  return (
    <>
      <h2>サインイン</h2>
      <form
        onSubmit={onSubmit(email, password)}
      >
        <TextInput
          label="メールアドレス"
          required
          value={email}
          type="email"
          onChange={inputEmail}
        />
        <TextInput
          label="パスワード(半角英数字6文字以上)"
          minLength={6}
          required
          value={password}
          type="password"
          onChange={inputPassword}
        />
        <div>
          <PrimaryButton label="サインイン" type="submit" />
        </div>
      </form>
    </>
  );
}

export default SignIn;

カスタムフックを利用した場合

では、カスタムフックを利用して書き換えてみましょう。

hooks.ts
import React, { useState, useCallback } from 'react';

export function useInputValue(initValue: string): [
  string,
  (e: React.ChangeEvent<HTMLInputElement>) => void
] {
  const [value, setValue] = useState(initValue);

  const updateValue = useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      setValue(event.target.value);
    },
    [setValue]
  );
  
  return [value, updateValue];
}

SignIn.tsx
import React from 'react';
import { TextInput } from '../../uiParts/TextInput';
import { PrimaryButton } from '../../uiParts/PrimaryButton';
import { TextLink } from '../../uiParts/TextLink';

/**
 * サインイン画面のコンポーネント
 * @return サインアップコンポーネント
 */
function SignIn() {
  const [email, updateEmail] = useInputValue('');
  const [password, updatePassword] = useInputValue('');

  return (
    <>
      <h2>サインイン</h2>
      <form
        onSubmit={onSubmit(email, password)}
      >
        <TextInput
          label="メールアドレス"
          required
          value={email}
          type="email"
          onChange={updateEmail}
        />
        <TextInput
          label="パスワード(半角英数字6文字以上)"
          minLength={6}
          required
          value={password}
          type="password"
          onChange={updatePassword}
        />
        <div>
          <PrimaryButton label="サインイン" type="submit" />
        </div>
      </form>
    </>
  );
}

export default SignIn;

どうでしょう?SignUp.tsxがとてもスッキリしてみやすくなったのではないでしょうか。コード量も減るので、TTIのパフォーマンスも上がります。

更に、カスタムフックを利用することでテスト書きやすくなるメリットがあります。
では、テスト書いてみましょう。今回は、JESTとtesting-libraryを利用します。

テストコード

hooks.test.ts
import { act, renderHook } from '@testing-library/react-hooks';
import { useInputValue } from './hook';

test('valueは初期値は0である', () => {
  const { result } = renderHook(() => useInputValue());
  expect(result.current[0]).toEqual('');
});


test('「test」が入力するとステートのvalueは「test」となる', () => {
  const { result } = renderHook(() => useInputValue());
  const updateValue = result.current[1];
  const mockInput = jest.fn(() => ({
    target: { value: 'test' },
  })) as unknown as () => React.ChangeEvent<HTMLInputElement>;
  
  act(() => {
    updateValue(mockInput());
  });
  expect(result.current[0]).toEqual('test');
});

こんな感じでテストコードも書けるので、堅牢なソースコードになります。

注意

カスタムフックにはいくつかルールがあります。

  • 命名は「use」から始める必要がある
  • 内部でほかのフックを呼びだす

React はカスタムフックもルールを違反してるかどうかを自動でチェックしてくれます。しかしこの命名規則を守らなかったらカスタムフックかどうか判別できなくなり、自動チェックもできなくなります。

詳しくは以下の公式ページ参照ください。

https://ja.reactjs.org/docs/hooks-custom.html

まとめ

どうだったでしょうか?
コード量も減り、ファイルも分割できて、可読性も上がる、パフォーマンスも上がるし、テストコードも書きやすいし、いいことしかありません。

ただ、抽出する際は、一つのフックは一つの事柄のタスクをこなしているかを注意しましょう。1つのフックでたくさんのタスクを行なっていると逆にわかりにくくなるし、テストも多くなり複雑になります。なので、そこを気をつけましょう!

これを機会に積極的にカスタムフックに抽出できないかを検討してみてはいかがでしょうか?:)

GitHubで編集を提案

Discussion