以前基本的なフックについて扱ったが、
いくつか基本以外のフックについても調べてみました。
基本のフックについては下記にて記載
useRef:値を保持する
const countRef = useRef(0); // 初期値0
countRef.current += 1; // .current で値を読み書き
// → 値は変わるが再レンダリングは起きない
どういう効果
レンダリングに関係しない値を保持するために使う
値が変更されても再レンダリングされないuseState
普通の変数と何が違うか
値を保持するだけ、値が変わっても再レンダリングしないなら変数でよいのでは?
と思ったが、どうやらコンポーネントが再レンダリングされた際に変数は保持されずに作り直されるが、
useRefを使うことで、レンダリングされたときでも保持されるという点が異なる。
つまり、ずっと値を持っておきたいという場合にuseRefを使う。
useStateとの使い分け
どちらも再レンダリングを行っても初期化されずに値を保持できる。
useStateとuseRefの使い分けは、画面表示で使うのか、裏側で保持するだけなのかの違い。
- 値を保持したい → useRef か useState
- 画面に反映したい → useState
- 画面に関係ない → useRef
コード例
useMemo:関数を必要な時だけ計算する
どういう機能か
関数を再レンダリング時に再計算しないようにする(重い処理の関数に主に使う)
特定の変数を監視し、その値が変更されたタイミングで再計算する
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
上記の場合todos,tabの2つの変数を監視(変数の変更を監視)し、値に変化があった場合に再計算
どんな時に使う
関数の処理が重い場合に処理抑制
軽い処理の場合はむしろ重くなるケースがある
コード例
多くの要素を持つ配列に対してフィルタリング処理を行うということをuseMemoの対象にした例
const filtered = useMemo(() => {
return products.filter((p) => p.name.includes(filterText));
}, [products, filterText]); // この2つが変わったときだけ再計算
大量のデータをフィルタリング例(CodeSandbox)
CodeSandboxコード備考:
コードを作ってやってみたもののデータが単純だからか違いはよくわからなかった。
(「テーマ切り替え」を押した時にuseMemoを使わないと押すたびに反映までのラグがあると想定したがよくわからず)
useCallback:関数の生成を必要な時だけにする
どういう機能か
関数を作り直さないよう関数定義をキャッシュする
useMemoは「関数の計算結果」をキャッシュしていたが、
useCallbackでは関数そのものをキャッシュする
どんな時に使う
主に再レンダリングを抑制するときに使う。
関数を子コンポーネントにpropsで渡す場合に、
同じ関数を渡すことが可能
(useCallbackを使用しない場合関数が毎回作成され、同じ処理の関数でも毎回別の関数として渡される)
これにより、子コンポーネントでpropsで受け取った関数に変化があったときだけ再描画するといったことが可能
補足)memo関数
よりuseCallbackを理解するためにはmemo関数を合わせて知っておく必要がある。
memo関数はpropsで受け取った値が変更がない場合にコンポーネントの再描画を行わないという関数。
memo(関数コンポーネント)
という形でコンポーネントをmemo関数で囲って使用。
囲うことでコンポーネントのpropsを前回渡された値と比較し、違いがあった場合だけコンポーネントが再レンダリングされる
import { memo } from 'react';
const SomeComponent = memo(function SomeComponent(props) {
// ...
});
上記コードではSomeComponentコンポーネントのpropsに変更があったときだけ再レンダリングされる
コード例
// パターン①:依存配列が空 → 最初の1回しか作られない
const handleClick = useCallback(() => {
console.log("クリック");
}, []);
// パターン②:依存配列に値あり → 指定した値が変わったときだけ再生成される
function ProductPage({ productId, referrer }) { // propsで受け取った値
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]); // productId か referrer が変わったときだけ再生成
}
[productId, referrer]は依存値のリスト。ここではproductId, referrerの2つの変数に対して変更があったときだけ関数が再度作成される。
Codesandbox例
useContext:propsを使わずに値を渡す
どういう機能か
コンポーネントの親子の階層が深い場合で値を渡す場合、propsリレーを行う必要がある
例:親→子→子→子→子→子(ここまでpropsで渡す)
useContextを使うことでpropsを使わずに離れたコンポーネントへ値を渡せるフック
理解するために必要な前提知識
useContextを使うには以下の3つがセットで登場する
① createContext → 「共有の棚」を作る関数
② Provider → 「棚に値を入れる」タグ
③ useContext → 「棚から値を取り出す」フック
createContext、Providerも知っておく必要があり簡単に解説
createContext
共有の棚(Context)を作る関数。
引数にはデフォルト値を渡す。
Providerが見つからない場合にこのデフォルト値が使われる。
const ThemeContext = createContext("light");
// ↑ デフォルト値(Providerがない場合に使われる)
Provider
createContextで作ったContextをタグとして使うことでProviderになる。
ラップしたコンポーネント全体に value の値を共有する。
// 古い書き方(今も動く)
<ThemeContext.Provider value={theme}>
<Page />
</ThemeContext.Provider>
// 新しい書き方(React19以降・推奨)
<ThemeContext value={theme}>
<Page />
</ThemeContext>
コード例
// ① 棚を作る(コンポーネントの外で定義)
const ThemeContext = createContext("light");
// ② 棚に入れる(渡す側のコンポーネント)
<ThemeContext value={theme}>...</ThemeContext>
// ③ 棚から取り出す(受け取る側のコンポーネント)
const theme = useContext(ThemeContext);
これを使うことで
App -> Page -> Section -> Card -> UserName
というコンポーネントの構成になっている場合に
Appで値(画面のテーマ)をセットしUserNameコンポーネントでpropsを使わず取得するということが可能
Codesandbox例
コンポーネントの階層が深いコンポーネントに値を渡す
まとめ
useRef
再レンダリングに関係しない値を保持するフック。
普通の変数はレンダリングのたびにリセットされるが、useRefはレンダリングをまたいで値を保持できる。
画面に反映する必要がない値の保持に使う。
useMemo
重い計算処理の結果をキャッシュするフック。
依存配列に指定した値が変わったときだけ再計算し、それ以外はキャッシュした結果を使い回す。
軽い処理に使うとキャッシュのコストが上回り逆効果になるため、重い処理にだけ使う。
useCallback
関数定義そのものをキャッシュするフック。
依存配列に指定した値が変わったときだけ関数を再生成し、それ以外は同じ関数を使い回す。
memo関数と組み合わせることで、子コンポーネントの無駄な再レンダリングを防ぐことができる。
useContext
propsを使わずに離れたコンポーネントへ値を渡せるフック。
階層が深いコンポーネントへのpropsリレーを解消できる。
createContext・Provider・useContextの3つをセットで使う。
感想
今回調べたフックではパフォーマンス向上のためのものが多かったです。
そういう意味では最低限抑えるべきフックでは確かになく、画面表示がどうしても重いなど後々使うかもという感じでしたが、こんなこともできるということを知っておく点ではよかったと思いました。