投稿日:2021年2月18日
HooksはReact16.8で追加されたstateやReactの機能を関数コンポーネントで使用できる機能です。この記事ではカスタムHooksを使い
Reactは16.8以降のバージョンではHooksという非常に便利な機能が追加されています。
この記事では自分でカスタムのHooksを作成する方法について解説しています。
この記事のコードは以下の環境を前提にしています。
実際に作り始める前にHooksを作る際の注意点がいくつかあるので説明しておきましょう。
Hooksのルールにあるとおり、Hooksはforループ、条件文、if-elseの内部などには入れずに、必ず関数コンポーネントのTOPレベルで呼び出せるようにする必要があります。
しかし、対象の機能がforループやif-elseの中で呼び出せるように作るほうが良い場合だってあるでしょう。そのような場合には、Hooksの作成を諦めてHOCで実装することを考えましょう!
Hooksの関数名は必ずuse〇〇のような名前にしましょう。
この名前にすることによって何かの機能が発動するわけではありません。
Hooksはまもらなければいけないルールがいくつかあります。
しかし、Hooksかそれ以外の関数かは実際の中身の処理しか違いがありません。ぱっと見でHooksとわかるような関数名にしておかないと、下の様に大変な事態になりそうなのは想像に難くないですね。
【Hooksに好きな関数名をつけてしまった世界線】
八神『え〜っと、Aの関数は中でuseStateを呼んでるからHooksだな。
Bの関数は…useStateとかは使ってないから普通の関数だな。if文の中で呼び出そう。』
石田「何やってるんだ八神!Bの関数はHooksだぞ!if文の中で呼び出すな!」
八神『何だって!?Bの関数はuseStateもuseEffectも使ってないぞ!?』
石田「Bの関数が呼び出しているCの関数の内部でuseEffectを使っているじゃないか!ちゃんと確認しろ!」
八神『…』
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を利用します。
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;
import React from "react";
import SampleComponent from "./components/SampleComponent";
function App() {
return (
<div className="App">
<SampleComponent></SampleComponent>
</div>
);
}
export default App;