useEffectのちらつきを無くしたいときの対処法【useLayoutEffect】
useEffectを使っていると初期表示時のちらつきが気になりますよね。そんな時に使えるのがuseLayoutEffectです。
なので、この記事ではuseEffectとuseLayoutEffectの違いについて解説します。
useLayoutEffectとuseEffectの違い
useEffectとuseLayoutEffectはコールバック関数が実行されるタイミングが違います。
useEffect
- ステータスを変更して、再レンダリングする
- Reactはレンダリングを開始する
- 画面に描画する
- useEffectを実行
useLayoutEffect
- ステータスを変更して、再レンダリングする
- Reactはレンダリングを開始する
- useLayoutEffectを実行
- 画面に描画する
つまり、useEffectは画面に表示された後に実行されるが、useLayoutEffectは画面に表示される前に実行される。これにより、useLayoutEffectを使えば初期表示時のちらつきが減らせるわけです。
レンダリングとは?
ここで一つ疑問が浮かびました。レンダリングって何をしているの?ということです。レンダリングは画面描画同じだと考えていましたが、実は違うみたいです。
レンダリングとはpropsとStateを基に、古いコンポーネントと新しく作成されるコンポーネントの差分を取ることです。これにより、差異がある部分だけの画面を更新でき、レスポンスが良くなります。
Reactはレンダリング出力をelementオブジェクトで保存し、古いツリーと新しいツリーの差分を収集します。
// JSX形式
return <SomeComponent a={42} b="testing">Text here</SomeComponent>
// JSX形式をBabelでコンパイル(上記と全く同じ動き)
return React.createElement(SomeComponent, {a: 42, b: "testing"}, "Text Here")
// elementオブジェクトで保存
{type: SomeComponent, props: {a: 42, b: "testing"}, children: ["Text Here"]}
そして、ここまでは裏で行われているため、画面には何の変更も反映されていません。この後にレンダリングで集めた差分をDOMに反映して、画面に表示されます。
useLayoutEffectとuseEffectを比較する
1.useLayoutEffect
useLayoutは画面が表示される前に実行するので、処理が重いと画面を表示するのに時間がかかります。以下のソースではuseLayoutEffectで5秒間スリープ状態を作っています。
import React, { useLayoutEffect, useState } from 'react';
//待ち時間の設定
const sleep = (waitMsec) => {
var startMsec = new Date();
while (new Date() - startMsec < waitMsec);
}
export default function App(){
const [text, setText] = useState("")
//画面描画前に実行
useLayoutEffect(() => {
sleep(5000); //5秒待
setText("表示されました!") //テキストの設定
});
return <p>{`text:${text}`}</p>
}
2.useEffect
一方、useEffectは画面に表示してから実行されるので、初期表示は早いです。
import React, { useEffect, useState } from 'react';
//待ち時間の設定
const sleep = (waitMsec) => {
var startMsec = new Date();
while (new Date() - startMsec < waitMsec);
}
export default function App(){
const [text, setText] = useState("")
//画面描画後に実行
useEffect(() => {
sleep(5000); //5秒待
setText("表示されました!") //テキストの設定
});
return <p>{`text:${text}`}</p>
}
useEffectとuseLayoutEffectの使い分け
スグに実行しなくていい処理や視覚的に影響を与えない処理にuseEffectを使い、初期表示した時点で絶対に実行しておきたい処理はuseLayoutEffectを使います。
また、実行時間がかかる処理はuseLayoutEffectよりも、useEffectのほうが推奨されます。実行が完了するまで画面が反映されないので。そのため、まずはuseEffectを使ってみて、問題が発生したらuseLayoutEffectを使うのがいいでしょう。
ちなみに、useEffectを使って遅延させて表示させるには条件付きでレンダリングを使用することです。
text && <p>{`text:${text}`}</p>
これで、textの値が取得するまでは画面に表示されないようにできます。
import React, { useEffect, useState } from 'react';
//待ち時間の設定
const sleep = (waitMsec) => {
var startMsec = new Date();
while (new Date() - startMsec < waitMsec);
}
export default function App(){
const [text, setText] = useState("")
//画面描画後に実行
useEffect(() => {
sleep(5000); //5秒待
setText("表示されました!") //テキストの設定
});
return (
<>
<p>画面は表示されました</p>
{text && <p>{`text:${text}`}</p>}
</>
)
}
まとめ
- useEffectは画面描画後に実行されるが、useLayoutEffectは画面描画前に実行される
- レンダリングは古いコンポーネントの状態と新しいコンポーネントの状態の差分を取得している。画面への繁栄はまだ。
- useEffectは画面の初期表示はスグだが、useLayoutEffectは処理がないと初期表示に時間がかかる。
参考
Discussion
なるほど!
こうやって捉えると、React のオフィシャルなドキュメントで言っていることがわかるように思えます!