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
に切り替えるのがベストです。