どちらもエフェクトを実行しますが、ブラウザのペイントに対するタイミングが異なり、そのタイミングの差こそが要点です。
useEffectはブラウザがペイントした後に実行されます。非同期かつ非ブロッキングです。ほぼすべての用途(データ、サブスクリプション、ロギング)に使います。useLayoutEffectは DOM が変更された後、ブラウザがペイントする前に同期的に実行されます。レイアウトを読み取り、同じフレーム内で DOM を変更して、目に見えるちらつきを避けたい場合に使います。
text
render → DOM updated → [useLayoutEffect runs] → browser paints → [useEffect runs]
なぜ useLayoutEffect が必要になるのか
要素を測定し、そのサイズに基づいてツールチップを再配置する場合を想像してください。useEffect だと、ユーザーは一瞬ツールチップが間違った位置に表示され、その後ジャンプするのを見てしまいます:
jsx
useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipTop(-height); // applied BEFORE paint → no visible jump
}, []);
useEffect だと、同じコードがペイント後に実行され、1 フレームのちらつきが生じます。
なぜデフォルトでは使うべきでないのか
- これはペイントをブロックします。
useLayoutEffect内の重い処理は、ブラウザがそれを終えるまでペイントできないため、UI がカクついて感じられます。 - SSR 中に警告を出します(レイアウトの存在しないサーバー上では実行できないため)。
経験則: デフォルトは useEffect を使い、useLayoutEffect は、そうしないとちらつく同期的な DOM の測定/変更にのみ使います。
