Part 6 · 副作用 (useEffect)
39 / 64 章
カスタムフック
自分でフックを作る。ただの関数の切り出し
ここまで useState、useEffect、useReducer と、いくつものフックを使ってきました。
これらはReact が用意したフックです。 そして自分でフックを作ることもできます。
新しい概念ではありません。ただの関数の切り出しです。
同じものが 2 回書かれている
画面幅を使う部品が 2 つあります。 (ブラウザの幅を変えると、両方の数字が変わります)
ウィンドウの幅を変えると、2 つとも光る
動きます。問題は、右のコードを見ると分かります。まったく同じ 8 行が、2 回書かれています。
const [width, setWidth] = useState(0);
useEffect(() => {
const update = () => setWidth(window.innerWidth);
update();
window.addEventListener("resize", update);
return () => window.removeEventListener("resize", update);
}, []);3 つめの部品でも幅が要るなら、また 8 行。後片付けの書き忘れが 1 か所でもあれば、そこだけ漏れます。
duplicated.tsx
"use client";
import { RenderBox } from "@/components/lesson/render-box";
import { useEffect, useState } from "react";
function WidthLabel() {
// ここから 8 行
const [width, setWidth] = useState(0);
useEffect(() => {
const update = () => setWidth(window.innerWidth);
update();
window.addEventListener("resize", update);
return () => window.removeEventListener("resize", update);
}, []);
// ここまで
return <RenderBox title="WidthLabel">いまの画面幅: {width}px</RenderBox>;
}
function DeviceLabel() {
// まったく同じ 8 行が、もう一度
const [width, setWidth] = useState(0);
useEffect(() => {
const update = () => setWidth(window.innerWidth);
update();
window.addEventListener("resize", update);
return () => window.removeEventListener("resize", update);
}, []);
return (
<RenderBox title="DeviceLabel">
判定: {width < 768 ? "スマホ向け" : "パソコン向け"}
</RenderBox>
);
}
export function Duplicated() {
return (
<div className="flex flex-col gap-2">
<WidthLabel />
<DeviceLabel />
</div>
);
}関数に切り出す
重複したコードを関数にまとめるのは、React に限らず普通のことです。 ここでも同じことをします。
まるごとコピーして、関数で包み、最後に return を付けただけです。 中身は一切変えていません。
useWindowWidth の中では useState とuseEffect を呼んでいます。フックの中でフックを呼べる——これがカスタムフックの正体です。
use-window-width.ts
"use client";
import { useEffect, useState } from "react";
/**
* 画面幅を返すカスタムフック。
*
* 名前が use で始まっていて、中で他のフックを呼んでいる。
* それだけで「カスタムフック」と呼ばれる。特別な仕組みはない。
*/
export function useWindowWidth() {
const [width, setWidth] = useState(0);
useEffect(() => {
const update = () => setWidth(window.innerWidth);
update();
window.addEventListener("resize", update);
// 後片付けもここに置いておける。使う側は知らなくてよい
return () => window.removeEventListener("resize", update);
}, []);
return width;
}使う側
const width = useWindowWidth();8 行が 1 行になりました。イベントの登録も、後片付けも、 使う側はもう知らなくてよくなっています。
動きも光り方も、まったく同じ
画面の動きは 1 ミリも変わっていません。変わったのは、読む人が背負う量です。
extracted.tsx
"use client";
import { RenderBox } from "@/components/lesson/render-box";
import { useWindowWidth } from "./use-window-width";
function WidthLabel() {
// 8 行が 1 行になった
const width = useWindowWidth();
return <RenderBox title="WidthLabel">いまの画面幅: {width}px</RenderBox>;
}
function DeviceLabel() {
const width = useWindowWidth();
return (
<RenderBox title="DeviceLabel">
判定: {width < 768 ? "スマホ向け" : "パソコン向け"}
</RenderBox>
);
}
export function Extracted() {
return (
<div className="flex flex-col gap-2">
<WidthLabel />
<DeviceLabel />
</div>
);
}いつ切り出すか
2 回目に同じものを書いたときが、いちばん分かりやすい目安です。 1 回目で切り出すと、まだ形が定まっていないことが多い。
もう 1 つ、重複していなくても切り出す価値がある場合があります。
- コンポーネントが読みにくくなっているとき。 画面の組み立てと、裏側の処理が混ざっている
- 名前を付けたら意味がはっきりするとき。
useWindowWidthと書いてあれば、中を読まずに済む
use-window-width.ts
"use client";
import { useEffect, useState } from "react";
/**
* 画面幅を返すカスタムフック。
*
* 名前が use で始まっていて、中で他のフックを呼んでいる。
* それだけで「カスタムフック」と呼ばれる。特別な仕組みはない。
*/
export function useWindowWidth() {
const [width, setWidth] = useState(0);
useEffect(() => {
const update = () => setWidth(window.innerWidth);
update();
window.addEventListener("resize", update);
// 後片付けもここに置いておける。使う側は知らなくてよい
return () => window.removeEventListener("resize", update);
}, []);
return width;
}理解できたか確かめる
確認クイズ
カスタムフックの決まりは?
確認クイズ
2 つの部品が同じカスタムフックを呼ぶと、state は?
確認クイズ
切り出す目安として適切なのは?
use-window-width.ts
"use client";
import { useEffect, useState } from "react";
/**
* 画面幅を返すカスタムフック。
*
* 名前が use で始まっていて、中で他のフックを呼んでいる。
* それだけで「カスタムフック」と呼ばれる。特別な仕組みはない。
*/
export function useWindowWidth() {
const [width, setWidth] = useState(0);
useEffect(() => {
const update = () => setWidth(window.innerWidth);
update();
window.addEventListener("resize", update);
// 後片付けもここに置いておける。使う側は知らなくてよい
return () => window.removeEventListener("resize", update);
}, []);
return width;
}この章のまとめ
- カスタムフックはただの関数の切り出し。 新しい仕組みではない
- 決まりは名前を
useで始めることだけ - 中で他のフックを呼べる。後片付けも中に閉じ込められる
- 共有されるのはロジックだけで、state は別々
- 世の中の
use〜ライブラリも、全部これ
use-window-width.ts
"use client";
import { useEffect, useState } from "react";
/**
* 画面幅を返すカスタムフック。
*
* 名前が use で始まっていて、中で他のフックを呼んでいる。
* それだけで「カスタムフック」と呼ばれる。特別な仕組みはない。
*/
export function useWindowWidth() {
const [width, setWidth] = useState(0);
useEffect(() => {
const update = () => setWidth(window.innerWidth);
update();
window.addEventListener("resize", update);
// 後片付けもここに置いておける。使う側は知らなくてよい
return () => window.removeEventListener("resize", update);
}, []);
return width;
}