初めに
ReactとNext.jsを業務で触っているのですが、最初の頃にHooksの種類が多くてどの時に何使えばいいの?と悩んだ時があったので自分のために整理してみます
1. useState
多分一番よく使う。
状態を管理するとよく言われる
公式の引用
state を使うと、ユーザの入力などの情報をコンポーネントに「記憶」させることができます。例えば、フォームコンポーネントは入力された文字を保持し、画像ギャラリのコンポーネントは選択された画像を保持できます。
サンプルコード
//const [状態変数, 状態を変更するための関数] = useState(状態の初期値);
const [count, setCount] = useState(initialState)
count
が状態を保持する変数
setCount
が状態を変更するための関数
右辺のinitialState
は状態の初期値
状態が更新されると コンポーネントが再レンダリング されます。
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}
useState(0)
で count
って名前のstateを初期値0で定義する
setCount
でcount
を更新できる
setCount
再レンダリングされる
再レンダリングとは?
- レンダリング = コンポーネントを実行して UI を計算すること
- React は state が更新されると、そのコンポーネント関数をもう一度呼び出して「新しい UI」を計算する
- その後、仮想DOM(UIのコピー)と実際のDOMを比較して、差分がある部分だけを更新する
- 全部のDOMを書き換える必要がなく、効率的に画面が更新される
こちらの説明がわかりやすかったです
https://qiita.com/seira/items/6767e222890c9890ecb9
2. useEffect
コンポーネント内で 副作用を扱うためのフック
「副作用」とは、画面の描画処理以外で実行したい処理のことを指します。
例:データの取得、イベントリスナーの登録、ログ出力、タイマー処理 など。
サンプルコード
import { useState, useEffect } from "react";
export default function EffectSample() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("countが変わった:", count);
return () => {
console.log("クリーンアップ:", count);
};
}, [count]);
return <button onClick={() => setCount(count + 1)}>+</button>;
第1引数・・・「副作用として実行したい処理」
第2引数・・・「依存配列」
実行タイミング(依存配列の違い)
空の配列が渡された場合
コンポーネントがレンダリングされる初回時のみに実行される
データフェッチや初期データの設定などで使われる
値あり 例:[count]
依存配列に設定しているcountが変更されるタイミングで実行される
useEffect(() => {
console.log("countが変わったときだけ実行");
}, [count]);
クリーンアップ処理
コンポーネントのアンマウント時(React コンポーネントが画面から取り除かれるタイミング)に実行される
useEffect(() => {
const handler = () => console.log("スクロール中…");
window.addEventListener("scroll", handler);
// クリーンアップ
return () => {
window.removeEventListener("scroll", handler);
};
}, []);
コンポーネントが画面から表示されなくなった時に登録したイベントも削除される
なぜ必要?
- クリーンアップをしないと、イベントリスナーが残り続ける
- コンポーネントが消えても処理が走り続ける
- 結果、メモリリークやパフォーマンス低下の原因になる
3. useRef
DOM要素や「レンダリングに影響しない値」を保持するためのフック
useRef
で保持した値は 更新しても再レンダリングされない。
サンプルコード
import { useRef } from "react";
export default function RefSample() {
const inputRef = useRef<HTMLInputElement>(null);
const focus = () => {
inputRef.current?.focus();
};
return (
<div>
<input ref={inputRef} />
<button onClick={focus}>フォーカス</button>
</div>
);
}
どんな時に使う?
1. DOM要素にアクセスしたい時
サンプルコードの例
DOM要素に対して直接アクセスして制御したい時に使います
2. 再レンダリングせずに保持したい時
初期処理などと特定のタイミングなどを表すフラグ値を覚えていたい時などに使う
const isMounted = useRef(false);
useEffect(() => {
if (!isMounted.current) {
console.log("初回マウント時だけ実行したい処理");
isMounted.current = true;
}
});
上記の処理state
で保持するとisMounted
が更新された時にレンダリングが走る
画面描画に関係ない処理なので本来レンダリングは不要
なので、useRef
を使って値を保持する
4. useMemo
計算コストの高い処理の結果をキャッシュ(メモ化) するためのフック
依存配列の値が変わったときだけ再計算し、それ以外は前回の結果を再利用する。
サンプルコード
import { useMemo } from "react";
function MovieList({ movies, searchTerm }) {
const filteredMovies = useMemo(() => {
return movies.filter(movie => movie.title.includes(searchTerm));
}, [movies, searchTerm]); // movies または searchTerm が変わった時だけ再計算
return (
<ul>
{filteredMovies.map(movie => (
<li key={movie.id}>{movie.title}</li>
))}
</ul>
);
}
なぜ使う必要がある?
無駄なレンダリングを防ぐため!
- リストのフィルタリング / ソート処理
- 重い計算(例:グラフ描画用データ計算)
など
useMemo を使わない場合の問題
サンプルコード
// useMemoを使わない場合
function MovieList({ movies, searchTerm }) {
const filteredMovies = movies.filter(movie =>
movie.title.includes(searchTerm)
);
return (
<ul>
{filteredMovies.map(movie => (
<li key={movie.id}>{movie.title}</li>
))}
</ul>
);
}
上記を親コンポーネントから使っている場合:
1.親コンポーネントの state が更新される
2. 親が再レンダリングされる
3. それに伴って MovieList も再レンダリングされる
4. movies が変わっていなくても filter が毎回実行される
重い処理や無駄に走らせたくない計算結果を保持したい時に使うと効果的
5. useCallback
関数を キャッシュ(メモ化) するためのフック
依存配列が変わらない限り、同じ関数インスタンスを再利用できる。
サンプルコード
// 親コンポーネント
function ParentComponent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log("クリック");
}, []);
return (
<>
<ChildComponent onClick={handleClick} />
<div>カウント: {count}</div>
</>
);
}
const ChildComponent = React.memo(({ onClick }) => {
console.log("子コンポーネントが再レンダリング");
return <button onClick={onClick}>クリック</button>;
});
上記の例だと、親コンポーネントのcountが更新されても子コンポーネントの再レンダリングは行わない。
なぜ使う必要がある?
無駄なレンダリングを防ぐため!
- 親コンポーネントが再レンダリングされると、通常は新しい関数インスタンスが毎回生成される
- その関数を props で子に渡すと「props が変わった」と判定されて子も無駄に再レンダリングされる
- useCallback を使えば関数をキャッシュできるので、子コンポーネントの無駄な再レンダリングを防げる
- React.memoと一緒に使わないと意味ないらしい
React.memoとは?
コンポーネントをラップして「props が変わらない限り再レンダリングをスキップ」できる
親が頻繁に再レンダリングされるけど、子の props は変わらないような場面で使うとパフォーマンスが上がる
まとめ
実務でよく使うhooksをまとめてみました。
基本的な内容ですが、useEffectの実行タイミングなどコード量が増えるとどこで実行されてるのかわかりづらいなと感じることが多いので、理解して使うようにしたいです。
React19から新しくhooksが追加されたらしいのでこちらも調べてみます