投稿日:2024/12/05 最終更新日:2024/12/08
【学習用】全React Hooksを簡単にまとめてみた
useState
usestateはコンポーネントにstate(状態)変数を追加するためのフックです。
ユーザ入力などの情報をコンポーネントに「記憶」させることができます。
また、state変数を追加するためのフックはuseState以外にuseReducerがあります。
const [state, setState] = useState(initialState);
使用例
import { useState } from 'react';
function MyComponent() {
const [age, setAge] = useState(10);
const [name, setName] = useState('太郎');
const [todos, setTodos] = useState(() => createTodos());
〜〜
initialStateはstate変数の初期値となり初回レンダー後は無視されます。
また、関数を指定した場合には初期化関数となり、引数を取ることなく何らかの値を返す必要があり、帰ってきた値がstate変数に初期値として格納されます。
set関数の使用
set関数を使用するとstate変数を更新し、再レンダーします。
function handleClick() {
// 太郎 → 一郎に変更
setName('一郎');
// 10 → 11に変更
setAge(a => a + 1);
};
更新前後で値が同じ場合は最適化のために再レンダーがスキップされます。
また、複数回set関数が呼び出される場合、それらはキューに予約され、レンダー前に呼び出しを行い最新の状態で表示させるため、set関数の呼び出しごとに再レンダーされることなく表示させることが出来る。
注意点
strict modeでは初期化関数が2回呼び出されます。これは純関数でない場合にエラーをはじき出すためのものであり、本番環境では影響しません。
下記のようにset関数の中身がstateの値を書き換えるような更新を行う場合はエラーとなるため、置き換える形でstateの更新を行う必要があります。
setTodos(prevTodos => {
// × これだと書き換えになってしまう
prevTodos.push(createTodo());
});
setTodos(prevTodos => {
// 〇 置き換える形なので破壊的ではない
return [...prevTodos, createTodo()];
});
また、set関数を呼び出して値の変更確認のためにログ出力をした場合、更新前のstateが出力されます。
なぜかというとset関数の結果は次レンダー時に反映されるため、set関数の呼び出し時はstateの更新が発生しないためです。
function handleClick() {
setName('次郎'); // 更新前のstate:"太郎"
console.log(name); // "太郎"が出力される
};
そしてstateの更新で再レンダリングが発生しない場合は、stateが配列である場合、参照渡しになっていないか確認します。
// ×(参照渡しとなるため再レンダリングは起こらない)
let getItem = newState;
// ⚪︎(値渡しとなるため再レンダリングは起こる)
let getItem = newState.slice(0, newState.length);
参照渡しになっている場合、Reactの比較アルゴリズムにおいて変更を行った場合でも変更は無かったと判断されるため、再レンダリングは起こりません。
なので、値を渡す形でコピーをするようにしましょう。
useEffect
useEffectは、コンポーネントを外部システムと同期させるためのReactフックのことです。
外部システムとはReact が制御していないコードのことを指し、非React製コンポーネントやタイマー・EventTargetインターフェイスのメソッドなどのことをいいます。
つまり、React が制御していないコードを同期させるために使用するReactフックを指し、もっと簡単に言うと「制御していない関数などの実行タイミングをReactのレンダリング後まで遅らせるフック」ということになります。
そのため、DOMの書き換えやAPI通信の処理などをはじめとする副作用の処理を扱うことが出来ます。
useEffect(setup, dependencies?);
使用例
第1引数にはクリーンアップ関数もしくは何も値を返さない純関数を記述します。
※クリーンアップ関数とはイベントリスナーやタイマーなどの関数を削除するための関数
第2引数には副作用関数を実行するタイミングを制御するために配列を記述します。例えば、配列内にstate変数などを入れておき、set関数によりstateの値が更新された場合はレンダリング後にuseEffect内の副作用関数を動かすなどを定義できます。他にもpropsやコンポーネント内に記述された変数なども使用出来ます。
また、第二引数に何も記載しない場合は毎回レンダリング後に副作用関数が動作しますが、空の配列を入れた場合は初回レンダリング後のみ副作用関数を動作させることも可能です。
// トップレベルで呼び出し
import { useEffect } from 'react';
import { createConnection } from './chat.js';
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
// useEffectを呼び出し
// レンダリング後にサーバーへの接続して最後に切断をする
useEffect(() => {
// 実行させたい副作用関数を記述
// 下記はサーバーへのリクエストを行うため外部システムとなる
const connection = createConnection(serverUrl, roomId);
connection.connect();
// クリーンアップ関数を定義
return () => {
connection.disconnect();
};
// 副作用関数の実行タイミングを制御する依存データを記述
// state変数とroomIDが更新されたときのレンダリング後に動かす
}, [serverUrl, roomId]);
// ...
}
注意点
外部システムとの同期をすることが無い場合はuseEffectの避けた方が良いです。
既存のpropsやstateから計算出来るものでuseEffectを使用する場合、無駄に再レンダリングを起こすことになりますので、通常の変数定義をして値を作成する形が望ましいです。
また、重い処理を伴う場合はuseMemoを使用してメモ化をすることで表示速度の遅延を抑えることが出来ます。
詳しくはこちら
あとこれも注意です
useLayoutEffect
useLayoutEffectの挙動については基本的にuseEffectと同じです。
使用例
import { useState, useRef, useLayoutEffect } from 'react';
function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0);
useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height);
}, []);
// ...
注意点
useLayoutEffectはuseEffectと挙動は同じですが、実行タイミングが異なります。
useEffectはレンダリング後(画面描画後)に実行をするフックですが、useLayoutEffectは画面描画前に実行されます。
useLayoutEffectは描画に関わるものである場合にuseEffectだと描画後に実行されるため、ちらつきなどが出てしまう場合があります。
その際はuseLayoutEffectを使用して描画前に処理をするようにすればいいですが、パフォーマンスの低下を招く恐れがありますので基本的にはuseEffectを使用してちらつきなどが出てしまう場合に描画に関わる部分をuseLayoutEffectで処理する形が望ましいです。
useCallback
useCallbackは再レンダー間でメモ化されたコールバック関数を返すためのフックです。
例えば、親コンポーネントが再レンダリングされたときに子コンポーネントも再レンダリングされることがあります。
このとき、子コンポーネントに渡されるpropsが頻繁に変化する場合、子コンポーネント内の関数も毎回新しく生成されてしまい、パフォーマンスが低下する可能性があります。
その時はuseCallbackを使うことで、再生成されるのを防げるのでパフォーマンスの向上が見込めます。
なので、レンダリングごとで処理が変わらないけど重たい処理を行う関数などはuseCallbackでラップするといいと思います。
使用例
第1引数はメモ化したい関数をいれます。
第2引数は関数が再生成されるべき依存配列を指定します。
import { useCallback } from 'react';
export default function ProductPage({ productId, referrer, theme }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]);
~~
useContext
useContextはコンポーネント間でデータの共有を行うためのフックです。
例えば「親>子>孫>ひ孫」のコンポーネントツリーがあったとして、親からひ孫までpropsを受け渡す場合、3回もバケツリレーを行わないといけません。
見た目自体もかなり複雑になってしまうため、このような場合はuseContextを利用することでグローバルな状態管理を行うことが出来、状態管理をしているコンポーネントから直接受け渡したいコンポーネント(例の場合はひ孫)に共有が出来ます。
使用例
使用するには3つの手順が必要です。
まずはcreateContextで新しいContextを作成します。
第1引数には初期値を設定します。
① Contextの作成
import { createContext } from 'react';
const ThemeContext = createContext('light');
次に最上位付近のコンポーネントにてProviderと呼ばれるコンポーネントを子コンポーネントにラップします。
これを行うことで子コンポーネント以下のコンポーネントにダイレクトでデータ共有が出来ます。
② Providerの利用
import { useState } from 'react';
import ThemeContext from './ThemeContext';
function App() {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext.Provider value={theme}>
{/* 子コンポーネント */}
</ThemeContext.Provider>
);
};
最後にデータを受け取りたいコンポーネントでuseContextフックを使用してContextの値を取得します。
こうすることでpropsのバケツリレーをしなくても下層のコンポーネントに値を共有することが出来ます。
③ 子コンポーネントでの利用
import ThemeContext from './ThemeContext';
function Button() {
const theme = useContext(ThemeContext);
return (
<button style={{ backgroundColor: theme }}>Click me</button>;
};
};
ちなみにuseContextが登場するまではReduxを使用してグローバルな状態管理を行うことが主流でした。
その後、useContextと後述するuseReducerの誕生により同じことが出来るようになりましたので、こちらの組み合わせを使用することが多くなりました。
useReducer
useReducerはuseStateと同様にstateを管理するために使用するフックです。
主な特徴としては複数のstateをまとめて管理することができます。
useStateの場合は管理するstateごとに宣言を必要としますが、useReducerの場合は1つの宣言で複数のstateを管理できるため、多くの状態を持つサイトなどでは管理がしやすくなるメリットがあります。
使用例
・単一のstateを扱う場合
import { useReducer } from 'react';
function MyComponent() {
const initialState = { count: 0 };
function reducerFunc(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
};
};
const [state, dispatch] = useReducer(reducerFunc, initialState);
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
<button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
</div>
);
};
・複数のstateを扱う場合
import { useReducer } from 'react';
function MyComponent() {
const initialState = { count: 0, otherCount: 100 };
function reducerFunc(state, action) {
switch (action.type) {
case 'increment':
return {
// 複数のstateを持っている場合は更新前のstateを展開する必要があります
...state,
count: state.count + 1
};
case 'decrement':
return {
...state,
count: state.count - 1
};
case 'multiplication':
return {
...state,
count: state.otherCount * 2
};
default:
return state;
};
};
const [state, dispatch] = useReducer(reducerFunc, initialState);
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
<button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
<button onClick={() => dispatch({ type: 'multiplication' })}>Multiplication</button>
</div>
);
};
また、先ほど書いたようにuseContextと併用することで複数のstateをグローバル管理することができます。
・useReducerとuseContextを併用する例
import React, { useReducer, useContext } from "react";
export const UserContext = createContext();
const initialState = {
name: "Ken",
age: 10
};
funcition reducerFunc(state, action) {
switch(action.type) {
case "nameChange":
return {
...state,
name: state.name = "Ichiro"
};
case "addAge":
return {
...state,
// 子コンポーネントで指定した数値をstateに加算します
age: state.age + action.payload
};
default:
return state;
};
};
export function UserProvider({ children }) {
const [state, dispatch] = useReducer(reducerFunc, initialState);
return (
<userContext.Provider value={state, dispatch}>
{/* 子コンポーネント */}
</ThemeContext.Provider>
);
};
使用する場面ですが複数の状態を管理する必要のある大規模サイトなどが対象になるかと思います。
少数の状態を管理する場合はuseReducerを使用するのはコストが割高なこともあるため、その場合はuseStateで管理した方が楽かなと思います。
また、小規模なサイトであればコンポーネントツリーも比較的浅めになるかと思うので、useContextではなくpropsで渡した方が良い場面もあります。
基本的には併用で使うことが多いかと思うので状態管理が大変な場合に使うことになると思います。
useId
useIdはアクセシビリティ属性に渡すことのできる一意なIDを作成するためのフックです。
端的に言えばid属性・htmlFor属性の紐付けの際などで被らない値を生成してくれます。
使用例
import { useId } from 'react';
function MyComponent() {
const id = useId();
return (
<div>
<label htmlfor="{id}">Username:</label>
<input id="{id}" type="text" />
</div>
);
};
このようにアクセシビティ対策のために使用されるものをuseIdを利用することで一意の値を利用することができるため、重複を排除することができます。
それ以外は基本的に使用することが少ないです。
また注意点として、key属性はデータから生成される必要があるためuseIdは使用できません。
このように限定的な場面での使用になりますが、アクセシビティの考慮は大切なので利用を想定しておくと良さそうです。
useMemo
useMemoとはレンダー間で計算結果をメモ化するためのフックです。
useCallbackと似ていますが、useMemoは値をメモ化するのに対して、useCallbackは関数をメモ化します。
こちらも値をメモ化することで不要なレンダリングを防ぐことができるのでパフォーマンスを最適化することができます。
使用例
下記を例にとると、expensiveCalculation関数の処理が非常に重たい場合にレンダリングされる度に重たい処理を行うことになるのでパフォーマンスの低下を起こす可能性があります。
そのため、useMemoでラップすることによってexpensiveCalculation関数により得られる値をメモ化するため、再レンダリング時にa, bの値が変化した場合のみ結果である場合にexpensiveCalculation関数を再実行させることができます。
なので、同じ値である場合には処理をスキップしてくれるので、パフォーマンス最適化が望めます。
import { useMemo } from 'react';
function MyComponent({ a, b }) {
const computedValue = useMemo(() => {
return expensiveCalculation(a, b);
}, [a, b]);
return (
<div>{computedValue}</div>
);
};
ちなみに依存配列を空で渡した場合にはuseEffectと同様に初回レンダリング時のみに実行して、それ以降のレンダリング時は同じ値を返します。
なので、高コストな処理で同じ結果を返すことが想定される場合には依存配列を空で渡したuseMemoを使うことが良いかと思います。
注意点
過度な使用はかえってパフォーマンスの低下を起こすことがあります。
メモ化すべき値は重たい処理によって返される値になりますので、その場合は比較的軽い処理であればuseMemoの使用は避けて、重い処理に使用を限定することで避けられます。
useRef
useRefはコンポーネントのレンダリングサイクルを通して値やDOMへの参照を保持するためのフックです。
useStateと似ている部分がありますが、useRefは値の保持のみであり再レンダリングは実行されません。
使用例
下記の場合、初回レンダリング時にinputにフォーカスを当てる処理が行われます。
inputのref属性には宣言されたinputRefが当てられており、inputRefにはinputのDOMが格納されています。
そのため、初回レンダリング時はinput.current = inputにfocusされる流れになります。
Reactだけでfocusを行うことはできないので、このようにuseRefを利用することがあります。
import { useRef, useEffect } from 'react';
function FocusInput() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return (
<input ref={inputRef} type="text" />
);
};
その他にも特定要素へのスクロールなどJavaScriptで行うようなDOMを用いた操作にてuseRefは使い所になります。
注意点
ただ単に値やDOMを保持しておいて再レンダリングをさせたくない場合ではuseRefで良いと思いますが、状態としてuseStateもしくはuseRefを検討する場合はuseStateの使用が良いかと思います。
個人的に使い分けの観点になりますが、状態保持はuseStateとメモ化を利用することで宣言的な設計になり、DOMの参照や保持は命令的な操作になるためuseRefで操作をするという棲み分けで考えています。
あまりこの部分は詳しくないため語ることは難しいですが、このような理由でuseRefはDOM操作などの時に使用するようにしています。
useOptimistic
useOptimisticとはUIを楽観的に更新するためのフックです。
楽観的更新とは非同期通信の結果が返ってくる前に予想される結果を先に反映してしまうことです。
これによりユーザーには表示の遅延が起こりにくくユーザー体験を向上させることが可能です。
使用例
下記の場合、ボタンをクリックするとAPIの呼び出しをしてから現在の数値に+1を行ってPOST通信をしています。
その後、サーバーから更新後の数値を取得してset関数に入れて表示する流れになっていますが、非同期通信を待たずとも結果は+1された結果であることはわかります。
なので、fetch関数の前にset関数を呼び出して状態の更新を行ってしまいます。
そして、非同期処理により再度set関数が呼び出されますが既に更新済みのため同じ状態なので、再レンダリングは起こらないということです。
ただ、非同期処理の結果によってはエラーになったりなど楽観的更新とは異なる結果が返ってくることがあります。
それに備えてエラーをキャッチしたら更新前の値をset関数に入れて整合性を保つようにしています。
import { useOptimistic } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
const increment = useOptimistic((prevCount) => {
setCount(prevCount + 1);
return fetch('/increment', {
method: 'POST',
body: JSON.stringify({ count: prevCount + 1 })
}).then(res =>
res .json()
).then(data => {
setCount(data.count);
}).catch(error => {
setCount(prevCount);
});
});
return (
<div>
<p>カウント: {count}</p>
<button onClick={increment}>カウントアップ</button>
</div>
);
};
注意点
まずは結果が予測できない場合には使用すべきではないです。
非同期処理の結果を待たずに何かしらの値を表示させておきたい場合、結果的に必ず楽観的更新による表示とは異なる結果が表示されるため、本来の意図ではない使われ方となります。
その場合はSuspenceを利用することが望ましいと思います。
また、エラーハンドリングの点で言えば、非同期処理の結果が思わぬものである場合にも楽観的更新とは異なる結果となるため、整合性を保てない場合がありますので注意が必要です。
useActionState
useActionStateとはフォームアクションの結果に基づいて状態を更新するためのフックです。
React19にて登場したフックであり現在は実験的なものをとして利用が出来ます。
フォーム送信の際の状態管理を一元的にまとめてくれるメリットがあります。
const [state, formAction, isPending] = useActionState(action, initialState, permalink?);
使用例
import { useActionState } from 'react';
async function increment(previousState, formData) {
return previousState + 1;
};
function StatefulForm() {
// フォーム送信時にincrementを実行・初期値は0
const [formState, formAction, isPending] = useActionState(increment, 0);
return (
// ボタンクリックをするとformActionにより指定の関数が動きます
<form onSubmit={formAction}>
// フォーム送信後のデータ処理が終わるまではLoadingが表示され完了後にstateが表示されます
{isPending ? "Loading..." : state}
// disabled={isPending}によりフォーム送信時は一時的にボタン操作を保留中にします
<button type="submit" disabled={isPending}>Increment</button>
</form>
);
};
このようにフォームでuseActionStateを使用した場合、フォーム送信後に状態を更新し、再レンダリングを走らせることが出来ます。
イメージとしてはuseStateと後述するuseTransitionを組みあわせたような動きをするフックです。
また、Next.jsにおいてuseActionStateとServer Actionsを利用することでユーザー操作に基づくデータフェッチを行うことも可能です。
"use server";
export async function searchProducts(_prevState, formData) {
const query = formData.get("name");
const res = await fetch(`https://dummyjson.com/products/search?q=${name}`);
const { products } = await res.json();
return products;
};
"use client";
import { useActionState } from "react-dom";
import { searchProducts } from "./actions";
export default function Form() {
const [products, action] = useActionState(searchProducts, []);
return (
<>
<form action={action}>
<label htmlFor="name">
商品検索
<input type="text" id="name" name="name" />
</label>
<button type="submit">Submit</button>
</form>
<ul>
{products.map((product) => (
<li key={product.id}>{product.title}</li>
))}
</ul>
</>
);
}
※引用(https://zenn.dev/akfm/books/nextjs-basic-principle/viewer/part_1_interactive_fetch)
本来データフェッチはServerComponentsで行うためユーザー操作が伴うのは適していない形になりますが、この形式であれば実現できます。
useDebugValue
useDebugValueとはReact Dev Tool上でカスタムフックにラベルを追加するためのフックです。
React Dev Toolとはブラウザ上で表示されているReactアプリケーションを構造的に把握することが出来、コンポーネント間で受け渡しされているpropsやstateなどを確認してデバックを行うことが出来るツールです。
つまりはReact Dev Tool上での確認をより視覚的に補助するためのフックとなります。
使用例
import { useState, useDebugValue } from 'react';
// MyCustomHookを読み出すコンポーネントをReact Dev Toolでインスペクトするとstateが表示される
function MyCustomHook(initialValue) {
const [count, setCount] = useState(initialValue);
useDebugValue(count);
return { count, setCount };
}
使用する場面は非常に限定的でデバック時にて使用することのみかと思います。
仮に様々なコンポーネントでデバック時に備えて作っておくみたいなことをすると、逆に可読性が悪くなり管理もむずかしくなります。
あくまでコンポーネントツリーが複雑で調査しづらい場面などで使用することを心得ておきたいです。
※あまり使わないかもしれないですが、、、
useDeferredValue
useDeferredValuegはUIの一部を遅延更新させるためのフックです。
負荷が伴うデータフェッチなどの処理は表示されるまでにタイムラグが発生することがあります。
そのような場合に高負荷の処理はバックグラウンドで動かしておき、処理が終わった段階で遅延更新することでパフォーマンスの最適化やユーザー体験の向上を図ることが出来ます。
使用例
入力欄に書き込みした際にset関数により入力された内容を即時にstateに反映をします。
ただ、useDeferredValueの宣言により生成されたdeferredTextは遅延更新となりますので、通常のstateと比べて遅れて表示されます。
もしtextの更新処理が重たい場合は更新されるまで1つ前の状態が表示され、遅延更新後に表示が切り替わる感じです。
※この例だと目に見える正直違いはないです
これがfetch関数を用いてデータ取得を行うなど処理が重いパターンの時などで有効です。
import { useState, useDeferredValue } from 'react';
function MyComponent() {
const [text, setText] = useState('');
// 遅延表示させたいstateを第1引数に入れると返り値のdeferredTextは遅延表示される
const deferredText = useDeferredValue(text);
return (
<div>
<input type="text" value={text} onChange={(e) => setText(e.target.value)} />
// 即時更新
<p>{text}</p>
// 遅延更新
<p>{deferredText}</p>
</div>
);
};
また、Suspenseと組み合わせて使用することでフォールバック状態など複雑な設定もできます。
useInperativeHandle
useInperativeHandleは子コンポーネントの情報を親コンポーネントに公開し、親→子の制御をするためのフックです。
親コンポーネントは子コンポーネントの内部状態や機能にアクセスすることが出来るようになると、状態変更などを実行できるようになります。
使用例
import React, { useRef, useImperativeHandle, forwardRef } from 'react';
// 子コンポーネント
// refを渡す必要があるためforwardRefでラップする
const Child = forwardRef((props, ref) => {
// useRefを宣言する
const inputRef = useRef(null);
// フォームにフォーカスをするsetFocus関数を定義
useImperativeHandle(ref, () => ({
setFocus: () => {
inputRef.current.focus();
}
}));
return (
<input ref="{inputRef}" props="" type="text" />
);
});
// 親コンポーネント
const Parent = () => {
const inputRef = useRef(null);
const handleClick = () => {
// 子コンポーネントに定義したsetFocus関数を呼び出す
inputRef.current.setFocus();
};
return (
<div>
<Child ref={inputRef} />
<button onClick={handleClick}>inputにフォーカスする</button>
</div>
);
}
useInperativeHandleはuseForwardと合わせて使用することが多いようです。
また、第3引数にはuseEffectのように依存配列を設定できるのでハンドルの再生成が可能です。
注意点
useInperativeHandleは基本的に使用しない方向性が良さそうです。
まずrefの使用によるパフォーマンス低下の可能性があるためです。
そのため、useRefの過度な使用は避けたいですし、useInperativeHandleも同じく避けるべきフックになります。
あと、コードの可読性が悪くもなりそうです。
子から親に情報を渡す流れはコンポーネントツリーから見ると逆の流れになるため、少しややこしくなるような気がします。
useInperativeHandleを使うなら独自のヘルパー関数を使用する方が良いかと思います。
useInsertionEffect
useInsertionEffectとはDOMのレイアウトが確定した後に副作用を実行するためのフックです。
同じような動作をするフックにはuseEffectとuseLayoutEffectがありますが、実行箇所がそれぞれ異なります。
useEffectが画面描画後に実行されるフックでAPIによるデータ取得などで利用されます。
useLayoutEffectは画面描画後に実行されるフックでuseEffectだとチラつきが出てしまう際に利用をします。
useInsertionEffectは同じくは画面描画後に実行されるフックになりますが、利用場面としてはCSS in JSを使用する場合になります。
useLayoutEffectの処理と同じタイミングでCSS in JSを実行すると変更前のスタイルだ適用されてしまうため、意図した反映にならないことがあります。
そのときはuseInsertionEffectを利用することでレイアウトエフェクトが発火する前に適用することが出来ます。
使用例
import { useInsertionEffect } from 'react';
// CSS in JS
function useCSS(rule) {
useInsertionEffect(() => {
// ここにCSSを記述
});
return rule;
};
注意点
CSS in JS利用時を想定しているため、そうでない場合はuseEffectを使うようにしましょう。
また、useInsertionEffect内部ではstateの更新を行うことはできません。
useSyncExternalStore
useSyncExternalStoreとはReact外部で管理されている状態を利用するためのフックです。
Reactコンポーネントでは通常useStateやuseReducerを利用して状態の管理を行いますが、これらを利用して状態管理をすることが出来ない外部ストアの場合はuseSyncExternalStoreを利用することで外部ストアを監視し、状態を参照することが出来ます。
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?);
使用例
import { useSyncExternalStore } from 'react';
// ①初期レンダリング時に外部ストアから状態を参照
// ④再レンダリング時に再び呼び出されて最新の状態を取得する
const getSnapshot = () => {
return /* 外部ストアのデータ */;
};
// ②外部ストアの状態が変更されたときに呼び出されるコールバック関数を登録
// ③呼び出されると再レンダリングをする
const subscribe = (callback) => {
// ...
};
function MyComponent() {
const value = useSyncExternalStore(subscribe, getSnapshot);
return (
<div>{value}</div>
);
};
注意点
useStateやuseReducerが使える場合にはuseSyncExternalStoreは使用しない方が良いです。
例えばReduxを利用する際やブラウザAPIなどの状態をReactコンポーネント側でも保持しておきたいといったときに使うべきかと思います。
うあくまで補助的な利用にとどめることが望ましいようです。
useTransition
useTransitionとはUIをブロックせずに状態を更新するために使用されるフックです。
例えば、状態更新が行われて再レンダリングが走った際に、処理が重たいとその間はユーザー操作が出来なくなります。
これのことをUIがブロッキングされた状態といいます。
こういった場面でuseTransitionを利用すると、状態更新が状態更新が行われて再レンダリングが走った際に、処理が重たくてもユーザー操作は出来るようにして、処理が終わり次第表示に反映されるようになります。
※useActionStateのペンディング状態を作り出すイメージです
const [isPending, startTransition] = useTransition();
使用例
import { useTransition, startTransition } from 'react';
function MyComponent() {
const [isPending, startTransition] = useTransition();
const [data, setData] = useState([]);
const fetchData = () => {
// 時間のかかる処理にstartTransitionをラップする
startTransition(() => {
fetch('/data').then(response =>
response.json();
).then(newData =>
// 取得したデータをset関数に渡して状態更新
setData(newData);
);
});
};
return (
<div>
// isPendingがtrueであれば「Loading...」を表示して、falseならDOM表示
// つまりfetchDataが走っている間はペンディング状態になり、終わると解除される
{isPending ? 'Loading...' : <ul>{data.map(item => <li key={item}>{item}</li>)}</ul>}
<button onClick={fetchData}>Fetch data</button>
</div>
);
};
注意点
複数の状態で使用すると予期しない動作を起こすことがあります。
また、その場合はペンディング状態が長くなりユーザー体験を悪化させることもあり得ますので、使用範囲は限定した方が良さそうです。
その他ではstartTransition内の状態更新は同期的でなくてはいけない(setTimeoutなどは使用できない)こと、startTransitionにわたす関数は同期実行されるため遅延実行することはできないなどがあります。