🔗

React 初心者の難問、カスタムフック(Custom Hook)を解説します

2021/08/27に公開
2

最近社内レビュー会で React レビューが多くなり、「カスタムフック使ったらスッキリできます」という言葉もよく聞くようになりました。

私が初めてそれを耳にしたときは「なにそれ美味しいの?」みたいな感じでしたし、初心者にはピンとこない概念かなーと思いましたので、今回のテーマにしたいと思います。


1. カスタムフックとは

カスタムフックは自分がカスタムして作るフックです。

React 公式サイトではカスタムフックをこう説明してます。

カスタムフックとは、名前が ”use” で始まり、ほかのフックを呼び出せる JavaScript の関数のことです。

でもこれだけ見たら絶対わからないと思うのでサンプルコートを一緒に見てみましょう。

2. チャットアプリの例

サンプルコートも React 公式サイトにあるものを持ってきました。

チャットアプリで友達がオンラインかオフラインかを示すメッセージを返すコンポーネントです。

import React, { useState, useEffect } from 'react';

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

useStateで state と更新関数を生成、useEffect内でAPIを購読して state を更新させてますね。そうやって更新されたisOnlineに沿って文字列を返してます。コード内容自体は今回メインテーマじゃないのでざっくり理解で大丈夫です。

問題は同じロジックを別のコンポーネントでも使うときです。例えば、友達リスト画面でオンライン状態のユーザー名は緑色にする要件があるとします。

上記のFriendStatusで使ったisOnlineロジックをコピペしたら実装はできるでしょう。

import React, { useState, useEffect } from 'react';

function FriendListItem(props) {
  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {props.friend.name}
    </li>
  );
}

ですが、同じコードが複数のコンポーネント内にあるのは気持ち悪いです。共通ロジックを別関数で作成し、コンポーネントではその関数を呼び出すだけの形にしたらキレイになりそうですね。

その共通ロジックを別関数で作成したものがカスタムフックです。

3. ロジックを切り出す

早速カスタムフックを作成してみましょう。

FriendStatusFriendListItemも、特定ユーザーの接続状態を判別するisOnlineが必要でした。だったらユーザーIDを引数に受け取ってisOnlineを返すフックを作ったら完璧です。

hooksディレクトリを作り、その配下にuseFriendStatus.jsxというファイルを作成します。

import { useState, useEffect } from 'react';

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}

上で見たコードそのままなので特に新しいことはありませんね。これでカスタムフック完成です。

3. カスタムフックのメリット

ロジックをカスタムフックで切り出したらいくつかメリットがあります。

3-1. キレイなコード

useFriendStatusを実際使ってみましょう。

function FriendStatus(props) {
 const isOnline = useFriendStatus(props.friend.id);

 if (isOnline === null) {
   return 'Loading...';
 }
 return isOnline ? 'Online' : 'Offline';
}
function FriendListItem(props) {
  const isOnline = useFriendStatus(props.friend.id);

  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {props.friend.name}
    </li>
  );
}

めちゃくちゃキレイになりました。やってること自体は共通ロジックコードをコピペした時と全く同じなのにキレイになったからいいですね。

このようにロジックを使い回すことができ、コードがスッキリすることが1番のメリットです。

3-2. 関心の分離

もう一つのメリットは、コンポーネントの役割を明確にできることです。

共通ロジックコードをただコピペした時は、一つのコンポーネントでロジック処理もUIレンダリングも担当してました。

それをカスタムフックでロジックを切り出したら、このように各コンポーネントの役割が明確になります。

私が書いた「React のカスタムフックで作る debounce 機能」という記事でもカスタムフックを紹介してますが、この処理は別に色んなところで使われるものではありませんでした。

それでも切り出した理由は、そうしないと一つのコンポーネントに処理が多くなり、サイズがデカくなりすぎると思ったからです。

このように必ず何回も使われるロジックではなくても、関心の分離のためにカスタムフックを作成するのも良い方法だと思います。

4. 注意点

結局カスタムフックというものはロジックを切り出した普通の Javascript 関数です。難しいことではありません。

ですが、やはり特別な部分があるのも事実なので注意点も話しておきたいです。

4-1. 命名は必ず "use" で始まる

カスタムフックもフックなので、フックのルールを守らないといけません。

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

カスタムフックの命名はは必ずuseで始まるようにしましょう。

4-2. 引数と返り値を自由に設定

カスタムフックは Javascript 関数なので引数も返り値も自由に決めることができます

引数・返り値の数や型はもちろん、必要なら引数や返り値がないフックも作れます。自分に都合いいように作成しましょう。

4-3. 同じカスタムフックを使っても state は別々

サンプルコートでFriendStatusFriendListItemuseFriendStatusを使ってました。

ですが、FriendStatusisOnlineFriendListItemisOnlineは別物です。つまり同じフックを使ってるコンポーネント同士が state を共有することはありません

「カスタムフックを使う」ことと「共通ロジックをコピペする」ことがやってること自体は同じだと話しましたね。これはコンポーネントたちが別々でuseStateuseEffectを読んでることを意味します。これが同じフックを使ってるコンポーネント同士が state を共有しない理由です。

React 公式サイトに書いてある通り、useStateuseEffectは複数回呼ぶことができてそれらは完全に独立しているということを覚えててください。


React 公式サイトの内容に私の説明を加えただけですが、まだカスタムフックに馴染んでない方のお役に立てたら嬉しいですー!

GitHubで編集を提案

Discussion

nabeyangnabeyang

はじめまして。

カスタムフックもフックなので、フックのルールを守らないといけません。

のフックのルールのリンク先だけ韓国語版になっているようです。

修正案

カスタムフックもフックなので、フックのルールを守らないといけません。

みんちゃんみんちゃん

今見たら遷移先も間違ってたのに気づいておりませんでした…お陰様で修正しました!ご指摘ありがとうございます!