React.jsのバッチ更新
コンポーネントの状態を更新するたびに、Reactの差分アルゴリズムが仮想DOMの変更を検出し、コンポーネントを実際のDOMでレンダリングすることは既にご存知でしょう。しかし、これは実際にすべての状態更新で発生しますか?または、Reactはこれらの更新をバッチ処理する方法を提供し、複数の状態の変更が1つのレンダリング呼び出しに結合されるようにしますか?
合成イベントにより、バッチ更新が可能になります
React 17以下は、これらの更新をバッチ処理するメカニズムを提供します。コーディング中にそれについてあまり考える必要はありませんが、Reactがコードを最適化する方法を知ることは、それでも素晴らしい学習です。
これが何が起こっているのかです。
- 複数の状態の変更がコールバック内で呼び出されます
- コールバックが合成イベントにアタッチされている場合、Reactはそれらのミューテーションをバッチ処理します
- ミューテーションがバッチ処理されると、レンダリング呼び出しは1回だけ行われます。
- それ以外の場合、各突然変異は新しいレンダリングにつながります
バッチ処理を可能にするには、状態の変更を合成Reactイベント内で呼び出す必要があります。簡単に言うと、合成Reactイベントは基本的に、ネイティブHTMLイベントの単なるラッパーであり、すべてのブラウザーで同じ機能を提供します。すべての合成イベントハンドラーは、propとしてコンポーネントに直接提供できます。
すべての動作を確認するために、Reactがすべての状態のミューテーションをミューテーションごとに1つのレンダーではなく、1つのレンダーに結合できるようにするコード例を次に示します。
function Component() {
const [id, setId] = useState(0);
const [date, setDate] = useState(new Date());
const [random, setRandom] = useState(Math.random());
// A lame callback, but it's just for
// the demo. Note that clicking on the
// button will only lead to a single
// re-render!
//
// This is b/c the callback is provided to
// 'onClick', a synthetic event.
const onButtonClick = () => {
setDate(new Date());
setId(prevId => prevId + 1);
}
console.log("Render:", id, date);
return (
<div>
<button
onClick={onButtonClick}>
Update state
</button>
</div>
);
}
//
// Almost the same as the previous
// example, but this time w/o
// a batched upate call.
//
function Component() {
const [id, setId] = useState(0);
const [date, setDate] = useState(new Date());
const [random, setRandom] = useState(Math.random());
// No batching, as the state
// mutations happend inside
// a setTimeout - even though
// the timeout is inside a
// synthetic event.
const onButtonClick = () => {
setTimeout(() => {
setDate(new Date());
setId(prevId => prevId + 1);
}, 0);
}
console.log("Render:", id, date);
return (
<div>
<button
onClick={onButtonClick}>
Update state
</button>
</div>
);
}
React18以降のバッチ更新
Reactの次のバージョンでは(少なくともこれを書いている間は)、バッチ処理のメカニズムが大幅に改善されます。合成イベントでのみ可能である代わりに、他のタイプのコールバックも使用できます。
- タイムアウト
- 約束
- ネイティブイベントハンドラ
この変更により、自動的に実行されるレンダリングが少なくなるため、アプリのパフォーマンスが向上する可能性があります。
React18以降でのバッチ更新の回避
React 18以降、状態の変化ごとに新しいレンダリングを明示的にトリガーするオプションがあり、バッチ更新を回避できます。これはエッジケースでのみ使用される可能性が高いですが、それでも知っておくと興味深いでしょう。
//
// This example is almost
// 1:1 from Dan Abramov's
// example.
//
// Use 'flushSync' from the
// 'react-dom'-package.
import { flushSync } from 'react-dom';
function handleClick() {
flushSync(() => {
setId(prevId => prevId + 1);
});
// React has updated the DOM by now
flushSync(() => {
setDate(new Date());
});
// React has updated the DOM by now
}