💡はじめに
Reactでは「Hooks」を使用することで、状態管理やライフサイクル処理などを関数コンポーネント内で簡単に実装できます。
Hooksを理解することで、Reactの動きやコンポーネントの仕組みを理解しやすくなります。
この記事では、Reactでよく使用されるHooksについて、簡単なサンプルコードを交えながら紹介していきます。
できるだけシンプルにまとめているので、React学習中の方の参考になれば幸いです。
✈️関連記事はこちら
React Hooks入門①
React Hooks入門③
👍こんな人におすすめ
・最近Reactを勉強し始めた
・Reactをもう少し理解したい
・使用経験のないHooksを理解したい
・さくっと学びたい
🚩React Hooks
目次
8.Custom Hook
9.useLayoutEffect
10.useImperativeHandle
11.useId
12.useTransition
13.useDeferredValue
14.useSyncExternalStore
8.Custom Hook
Custom Hookは、複数のコンポーネントで共通利用したい処理をまとめるための機能です。
Reactでは、Hookを組み合わせて独自のHookを作成できます。
同じ処理を何度も書かなくてよくなるため、コードの再利用性を高められます。
ただし、複数のコンポーネントで呼び出した場合、それぞれ独立したstateを持ちます。
同じ状態を共有したい場合は、useContextや外部ストアなどを組み合わせます。
import { useState } from 'react';
const useCounter = () => {
const [count, setCount] = useState(0);
const increment = () => {
setCount((prev) => prev + 1);
};
const decrement = () => {
setCount((prev) => prev - 1);
};
return {
count,
increment,
decrement
};
};
export default useCounter;
import './App.css';
import useCounter from './useCounter';
function App() {
const { count, increment, decrement } = useCounter();
return (
<div>
<h1>customHook</h1>
<p>count:{count}</p>
<button onClick={increment}>
+1
</button>
<button onClick={decrement}>
-1
</button>
</div>
);
}
export default App;
customHookは通常の関数と同じように作成できます。
ただし、ReactのHookであることを分かりやすくするため、useから始める命名ルールがあります。
const useCounter = () => {
今回のサンプルでは、カウント機能をuseCounterにまとめています。
コンポーネント側では、Hookを呼び出すだけで状態管理の処理を利用できます。
const { count, increment, decrement } = useCounter();
例えば以下のような場面で利用されます。
・API通信処理
・フォーム管理
・認証処理
・共通状態管理
同じ処理を複数のコンポーネントで使いたい場合によく利用されます。
9.useLayoutEffect
useLayoutEffectは、画面描画前に処理を実行できるHookです。
基本的な使い方はuseEffectと似ていますが、実行されるタイミングが異なります。
・useEffect
→画面描画後に実行
・useLayoutEffect
→DOMの更新後、ブラウザが画面に描画する前に実行
DOMサイズの取得やレイアウト調整などで利用されます。
import { useLayoutEffect, useRef } from 'react';
import './App.css';
function App() {
const boxRef = useRef(null);
useLayoutEffect(() => {
// 要素の横幅を取得
const width = boxRef.current.offsetWidth;
console.log(`横幅:${width}px`);
}, []);
return (
<div>
<h1>useLayoutEffect</h1>
<div
ref={boxRef}
style={{
width: '300px',
padding: '20px',
backgroundColor: '#ddd'
}}
>
サンプル要素
</div>
</div>
);
}
export default App;
useLayoutEffectは以下のように記述します。
useLayoutEffect(() => {
実行したい処理
}, []);
今回の場合は、コンポーネント表示時にDOM要素の横幅を取得しています。
const width = boxRef.current.offsetWidth;
useLayoutEffectは画面描画前に実行されるため、DOMサイズ取得やレイアウト調整を行いたい場合に利用されます。
例えば以下のような場面で利用されます。
・DOMサイズ取得
・スクロール位置調整
・アニメーション制御
・レイアウト調整
通常はuseEffectを使用し、DOM描画前に処理したい場合のみuseLayoutEffectを使用します。
10.useImperativeHandle
useImperativeHandleは、親コンポーネントから子コンポーネントの関数や値を操作できるようにするHookです。
useImperativeHandleは便利ですが、親から子を直接操作するため、使いすぎるとコンポーネント間の結合が強くなります。
通常はpropsで制御し、inputのfocus制御や動画再生制御など、命令的な操作が必要な場合に利用します。
import { useRef } from 'react';
import './App.css';
import CustomInput from './CustomInput';
function App() {
const inputRef = useRef(null);
// 子コンポーネントの関数を実行
const handleClick = () => {
inputRef.current?.focusInput();
};
return (
<div>
<h1>useImperativeHandle</h1>
<CustomInput ref={inputRef} />
<br /><br />
<button onClick={handleClick}>
フォーカス
</button>
</div>
);
}
export default App;
import {
forwardRef,
useImperativeHandle,
useRef
} from 'react';
const CustomInput = forwardRef((props, ref) => {
const inputRef = useRef(null);
// 親コンポーネントへ公開する処理
useImperativeHandle(ref, () => ({
focusInput() {
inputRef.current?.focus();
}
}));
return (
<input
type="text"
ref={inputRef}
/>
);
});
export default CustomInput;
useImperativeHandleは以下のように記述します。
useImperativeHandle(ref, () => ({
公開したい処理
}));
今回の場合は、focusInput()を親コンポーネント側へ公開しています。
focusInput() {
inputRef.current?.focus();
}
親コンポーネントではrefを使用することで、子コンポーネントの関数を実行できます。
inputRef.current?.focusInput();
例えば以下のような場面で利用されます。
・inputのフォーカス制御
・モーダル操作
・動画再生制御
・子コンポーネントの関数実行
親コンポーネントから子コンポーネントを直接操作したい場合に利用されるHookです。
11.useId
useIdは、一意なIDを生成できるHookです。
配列をmapで表示する際のkeyとしては使用しません。
keyにはデータが持つIDなど、要素を一意に識別できる値を使用します。
useIdはSSRでも安定したIDを生成できるため、サーバー側とクライアント側でIDがずれる問題を防ぎやすくなります。
import { useId } from 'react';
import './App.css';
function App() {
// 一意なIDを生成
const id = useId();
return (
<div>
<h1>useId</h1>
<label htmlFor={id}>
ユーザー名
</label>
<br />
<input
id={id}
type="text"
/>
</div>
);
}
export default App;
useId()を使用することで、一意なIDを生成できます。
const id = useId();
生成したIDをlabelとinputに設定することで、関連付けが行えます。
<label htmlFor={id}>
<input id={id} />
今回の場合、labelをクリックするとinputへフォーカスが移動します。
例えば以下のような場面で利用されます。
・フォーム部品のID生成
・labelとinputの関連付け
・アクセシビリティ対応
・重複しないID管理
フォーム部品を扱う場合によく利用されるHookです。
12.useTransition
useTransitionは、優先度の低い更新処理を後回しにできるHookです。
startTransitionは、内部のstate更新を優先度の低い更新として扱います。
ただし、startTransition内に書いた重いJavaScript処理そのものが別スレッドで実行されるわけではありません。
重い計算処理自体を軽くしたい場合は、useMemo、仮想スクロール、Web Workerなども検討します。
import { useMemo, useState, useTransition } from 'react';
import './App.css';
function App() {
const [text, setText] = useState('');
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();
const list = useMemo(() => {
const items = [];
for (let i = 0; i < 20000; i++) {
items.push(`${query}-${i}`);
}
return items;
}, [query]);
const handleChange = (e) => {
const value = e.target.value;
// 入力欄の更新は優先度高く更新
setText(value);
// リスト更新は優先度を下げる
startTransition(() => {
setQuery(value);
});
};
return (
<div>
<h1>useTransition</h1>
<input
type="text"
value={text}
onChange={handleChange}
/>
{isPending && <p>更新中...</p>}
<ul>
{list.map((item) => (
<li key={item}>
{item}
</li>
))}
</ul>
</div>
);
}
export default App;
useTransitionは以下のように記述します。
const [isPending, startTransition] = useTransition();
| 名前 | 内容 |
|---|---|
| isPending | 処理中かどうか |
| startTransition | 優先度を下げて実行する関数 |
例えば以下のような場面で利用されます。
・検索機能
・大量データ表示
・フィルタ処理
・パフォーマンス改善
重い処理で画面操作が遅くなる場合に利用されるHookです。
13.useDeferredValue
useDeferredValueは、値の更新の優先度を下げ、Reactが余裕のあるタイミングで反映できるようにするHookです。
debounceやsetTimeoutのように、指定した時間だけ遅らせるHookではありません。
Reactが緊急度の高い更新を優先し、余裕のあるタイミングで遅延した値を更新します。
import {
useDeferredValue,
useMemo,
useState
} from 'react';
import './App.css';
function App() {
const [text, setText] = useState('');
// 更新を遅延
const deferredText = useDeferredValue(text);
// 遅延した値でリスト生成
const list = useMemo(() => {
const items = [];
for (let i = 0; i < 20000; i++) {
items.push(deferredText);
}
return items;
}, [deferredText]);
return (
<div>
<h1>useDeferredValue</h1>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
/>
<ul>
{list.map((item, index) => (
<li key={index}>
{item}
</li>
))}
</ul>
</div>
);
}
export default App;
useDeferredValue()を使用することで、値の更新タイミングを遅らせられます。
const deferredText = useDeferredValue(text);
今回の場合は、入力値textをそのまま使用せず、遅延したdeferredTextを利用しています。
const list = useMemo(() => {
const items = [];
for (let i = 0; i < 20000; i++) {
items.push(deferredText);
}
return items;
}, [deferredText]);
これにより、input入力を優先しながら重い描画処理を実行できます。
例えば以下のような場面で利用されます。
・検索機能
・大量データ表示
・リアルタイムフィルタ
・パフォーマンス改善
入力操作をスムーズに保ちながら重い処理を実行したい場合に利用されるHookです。
14.useSyncExternalStore
useSyncExternalStoreは、外部ストアの状態をReactと同期するためのHookです。
Reduxや独自ストアなど、React外で管理しているデータを安全に取得したい場合に利用されます。
import { useSyncExternalStore } from 'react';
import {
getSnapshot,
increment,
subscribe
} from './store';
import './App.css';
function App() {
// 外部ストアと同期
const count = useSyncExternalStore(
subscribe,
getSnapshot
);
return (
<div>
<h1>useSyncExternalStore</h1>
<p>count:{count}</p>
<button onClick={increment}>
カウントアップ
</button>
</div>
);
}
export default App;
let count = 0;
// 購読リスト
let listeners = [];
// count取得
export const getSnapshot = () => {
return count;
};
// count更新
export const increment = () => {
count++;
listeners.forEach((listener) => listener());
};
// 購読
export const subscribe = (listener) => {
listeners.push(listener);
return () => {
listeners = listeners.filter((l) => l !== listener);
};
};
useSyncExternalStoreは以下のように記述します。
useSyncExternalStore(
subscribe,
getSnapshot
);
| 引数 | 内容 |
|---|---|
| subscribe | 状態変更の監視 |
| getSnapshot | 現在の状態取得 |
今回の場合は、外部ストアのcountをReactコンポーネントへ同期しています。
const count = useSyncExternalStore(
subscribe,
getSnapshot
);
ボタンをクリックすると外部ストア側のcountが更新され、React側の画面も自動更新されます。
例えば以下のような場面で利用されます。
・Redux連携
・グローバル状態管理
・外部ストア同期
・リアルタイムデータ管理
React外で管理している状態を扱う場合に利用されるHookです。
✈️関連記事
React Hooks入門①