Reactを使っていると頻繁に登場するフックuseEffect。
しかし、似たような名前で「どっちを使えばいいの?」と迷いやすいのがuseLayoutEffectです。
名前は似ていますが、両者には実行されるタイミングに明確な違いがあります。
この記事では『useLayoutEffect』について、以下の内容を初心者にもわかりやすく解説します。
useLayoutEffectとは?useLayoutEffectの特徴useEffectとuseLayoutEffectの違い
useLayoutEffectとは?
useLayoutEffectは、ReactがDOMを更新した直後、ブラウザが画面を描画する前に同期的に実行されるフックです。
使い方の構文自体はuseEffectとほとんど変わりません。ただし動作のタイミングが異なるため、以下のような場面に適しています。
- ユーザーに画面が表示される前に処理を実行したいとき
- 要素のサイズや位置など、正確なレイアウト計測を行いたいとき
- スタイルの調整を「ちらつきなく」行いたいとき
サンプルコード(幅の計測)
import { useLayoutEffect, useState } from "react";
function App() {
const [width, setWidth] = useState(0);
useLayoutEffect(() => {
// DOMが配置された直後に幅を測定
const element = document.getElementById("box");
if (element) {
setWidth(element.offsetWidth);
}
}, []);
return (
<div>
<div id="box" style={{ width: "200px", border: "1px solid black" }}>
サンプルボックス
</div>
<p>幅: {width}px</p>
</div>
);
}
export default App;このように「描画前に正確なレイアウト計測をしたい」ときに使われます。
useLayoutEffectの特徴
useLayoutEffectの特徴を以下に示します。
- 同期的に実行される
- 処理が完了するまでブラウザの描画が止まる。
- ちらつきを防げる
- DOMのレイアウトやスタイルを描画前に変更できるため、一瞬表示が変わる「フラッシュ」を防止できる。
- DOMのレイアウト操作や計測に適している
- 要素の幅や高さ、スクロール位置などを正確に扱いたい場面に有効。
- パフォーマンスへの影響に注意
- 描画がブロックされるため、重い処理を実行すると体感速度が遅くなり、ユーザー体験を損ねる可能性がある。
useEffectとuseLayoutEffectの違い
useEffectとuseLayoutEffectの一番大きな違いは副作用(Effect)の実行タイミングにあります。このタイミングの違いが、「ちらつきが起きるかどうか」「パフォーマンスにどう影響するか」に直結します。
| 項目 | useEffect | useLayoutEffect |
|---|---|---|
| 実行タイミング | コンポーネントのレンダリングが完了し、ブラウザが画面を描画した「後」に非同期的に実行される。 | Reactが仮想DOMを実際のDOMに反映した直後、ブラウザが画面に描画する「前」に同期的に実行される。 |
| 主な用途 | ・APIからのデータ取得 ・イベントリスナーの登録・解除 ・ログ出力や外部サービスへの通信 | ・要素の幅・高さ・位置などのレイアウト計測 ・スタイルを描画前に調整して「最初から正しい見た目」にする |
| メリット | 描画を妨げないためパフォーマンスに優れる | ちらつきを防げる(画面が最初から整った状態) |
| デメリット | DOMがすでにユーザーに表示された後に動くため、要素のサイズ調整やスタイル変更を行うと「一瞬ズレてから修正される」ようなちらつき(フラッシュ現象)が発生することがある。 | 描画が「一時停止」して処理が完了するまで待つため、ここに重い処理を入れるとページ全体の描画が遅れ、パフォーマンスを悪化させる可能性がある。 |
次の例でuseEffectとuseLayoutEffectの違いを体感できます。
useEffectの場合(ちらつきが発生)
import { useEffect, useState } from "react";
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
if (count === 0) {
setCount(Math.floor(Math.random() * 100) + 1);
}
}, [count]);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(0)}>0にリセット</button>
</div>
);
}
export default App;挙動の流れ
- 初期レンダリング
countの初期値は0なので、仮想DOM上の<p>{count}</p>には「0」が設定される。
- ブラウザの描画処理
- Reactが生成したDOMがブラウザに渡され、ユーザーの画面に「0」が一度表示される。
useEffectの発火- 描画が完了した後に
useEffectが実行され、setCountによってランダムな数値がセットされる。
- 描画が完了した後に
- 再レンダリング
- Reactが再レンダリングを行い、仮想DOM上の
<p>がランダムな数値に置き換えられる。
- Reactが再レンダリングを行い、仮想DOM上の
- 再描画
- ブラウザが更新後のDOMを描画し、画面上の「0」がランダムな数値に切り替わる。
このとき、一瞬だけ0 → ランダムな数値に切り替わるため、ユーザーの目には「ちらつき(フラッシュ現象)」として映る。
同様に、ボタンを押してsetCount(0)を実行した場合も、
- 一瞬
<p>に0が表示される - すぐにランダムな数値へ切り替わる
という流れになり、やはりちらつきが起きます。
この「ちらつき」はuseEffectがブラウザの描画完了後に実行されるために発生しています。
useLayoutEffectの場合(ちらつきなし)
import { useLayoutEffect, useState } from "react";
function App() {
const [count, setCount] = useState(0);
useLayoutEffect(() => {
if (count === 0) {
setCount(Math.floor(Math.random() * 100) + 1);
}
}, [count]);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(0)}>0にリセット</button>
</div>
);
}
export default App;挙動の流れ
- 初期レンダリング
countの初期値は0なので、仮想DOM上の<p>{count}</p>には「0」が設定される。
useLayoutEffectの発火- Reactが実際のDOMを更新した直後、ブラウザが描画を行う前に同期的に
useLayoutEffectが実行される。 - ここで
setCountによってランダムな数値がセットされる。
- Reactが実際のDOMを更新した直後、ブラウザが描画を行う前に同期的に
- 再レンダリング
- Reactが即座に再レンダリングを行い、仮想DOM上の
<p>が「ランダムな数値」に置き換わる。
- Reactが即座に再レンダリングを行い、仮想DOM上の
- ブラウザの描画処理
- ブラウザが画面を描画する。この時、すでに
<p>はランダム値に更新済みなので、ユーザーが目にするのは最初からランダムな数値。
- ブラウザが画面を描画する。この時、すでに
つまり、ブラウザに実際の描画が渡る前に値が更新されるので、ユーザーにはちらつきが一切見えない。
本記事のまとめ
この記事では『useLayoutEffect』について、以下の内容を説明しました。
useEffect: 描画後に実行(非同期)。主にデータ取得やイベント登録向き。useLayoutEffect: 描画前に実行(同期)。DOM 計測やスタイル調整に使う。
基本的にuseEffectを使い、「DOMを扱う処理でちらつきが気になるとき」だけuseLayoutEffectに切り替えるのがベストです。