※このブログではサーバー運用、技術の検証等の費用のため広告をいれています。
記事が見づらいなどの問題がありましたらContactからお知らせください。


【TypeScript in React】カスタムHooksでコンポーネントの処理を関数化

react web開発 フロント開発 TypeScript hook

投稿日:2021年2月18日

このエントリーをはてなブックマークに追加
HooksはReact16.8で追加されたstateやReactの機能を関数コンポーネントで使用できる機能です。この記事ではカスタムHooksを使い

はじめに

Reactは16.8以降のバージョンではHooksという非常に便利な機能が追加されています。

この記事では自分でカスタムのHooksを作成する方法について解説しています。

環境

この記事のコードは以下の環境を前提にしています。

  • typescript version 4.1.5
  • react version 17.0.1

Hooks作成の注意点

実際に作り始める前にHooksを作る際の注意点がいくつかあるので説明しておきましょう。

TOPレベルで呼べるようなHooksにする

Hooksのルールにあるとおり、Hooksはforループ条件文if-elseの内部などには入れずに、必ず関数コンポーネントのTOPレベルで呼び出せるようにする必要があります。

しかし、対象の機能がforループやif-elseの中で呼び出せるように作るほうが良い場合だってあるでしょう。そのような場合には、Hooksの作成を諦めてHOCで実装することを考えましょう!

Hooks名

Hooksの関数名は必ずuse〇〇のような名前にしましょう。
この名前にすることによって何かの機能が発動するわけではありません。

Hooksはまもらなければいけないルールがいくつかあります。
しかし、Hooksかそれ以外の関数かは実際の中身の処理しか違いがありません。ぱっと見でHooksとわかるような関数名にしておかないと、下の様に大変な事態になりそうなのは想像に難くないですね。

【Hooksに好きな関数名をつけてしまった世界線】

八神『え〜っと、Aの関数は中でuseStateを呼んでるからHooksだな。
Bの関数は…useStateとかは使ってないから普通の関数だな。if文の中で呼び出そう。』

石田何やってるんだ八神!Bの関数はHooksだぞ!if文の中で呼び出すな!

八神『何だって!?Bの関数はuseStateもuseEffectも使ってないぞ!?』

石田Bの関数が呼び出しているCの関数の内部でuseEffectを使っているじゃないか!ちゃんと確認しろ!

八神『…』


実践

./hooks/useFetch.tsx
import { useEffect, useState } from "react";

/**
 * データをfetchしjsonデータを返すGeneric型のHook
 * @param url fetchするAPIのURL
 * @param callback
 * @returns {P}
 */
function useFetch<P>(url: string, callback?: (data: P) => void) {
  // stateの設定
  const [data, setData] = useState<P | null>(null);
  useEffect(() => {
    if (callback) {
      callback(data as P);
    }
  }, [data]);

  // fetch処理
  fetch(url)
    .then((response) => {
      if (response.ok) {
        return response.json();
      }
    })
    .then((data) => {
      setData(data);
    });

  return data;
}

export default useFetch;

useFetch関数はデータをフェッチする対象のurlとデータがフェッチされた後に実行されるcallback関数を受け取ります。さらにこの関数はGeneric関数で型パラメータとしてデータの型Pを指定しています。もちろんGeneric型にせず通常の関数として定義することもできますが、その場合この関数から返されるデータはany型になってしまいます。TypeScriptではany型はできる限り避けるべきです

では実際にこのHooksを使って見ましょう。

今回はサンプルのAPIとしてJSONPlaceholderを利用します。

./components/SampleComponents.tsx
import React, { useState } from "react";
import useFetch from "../hooks/useFetch";

/**
 * APIのレスポンスデータの型
 */
type ResponseDataType = Array<{
  userId: number;
  id: number;
  title: string;
  body: string;
}>;

/**
 * JSONPoaceholderのAPIからpost一覧を取得し、データを表示するコンポーネント
 */
const SampleComponent = () => {
  // fetchで取得できたデータ数
  const [dataLen, setDataLen] = useState(0);

  // カスタムfetchの使用
  const data = useFetch<ResponseDataType>(
    "https://jsonplaceholder.typicode.com/posts",
    (d) => {
      if (d) {
        setDataLen(d.length);
      }
    }
  );
  if (data == null) {
    return <div>データ取得中…</div>;
  }
  return (
    <div>
      <b>データ数:{dataLen}</b>
      <hr></hr>
      <div>
        {data.map((post, i) => {
          return (
            <>
              <div>userId={post.userId}</div>
              <div>id={post.id}</div>
              <div>title={post.title}</div>
              <div>body={post.body}</div>
              <hr></hr>
            </>
          );
        })}
      </div>
    </div>
  );
};

export default SampleComponent;
./App.tsx
import React from "react";
import SampleComponent from "./components/SampleComponent";

function App() {
  return (
    <div className="App">
      <SampleComponent></SampleComponent>
    </div>
  );
}

export default App;
このエントリーをはてなブックマークに追加


関連記事

記事へのコメント