React 18の新機能Automatic Batchingを理解する
React 18で追加予定の「Automatic Batching」。複数のステート更新をひとつのレンダリングにまとめることで再描画を抑制する機能で、Reactアプリケーションのパフォーマンスを向上することができそうです。調べてみました。
React 18のプランが公開に
Reactの次のメジャー・リリースとなるReact 18について告知が出ました。
さまざまな更新が予定されていますが、この記事ではAutomatic Batching = バッチ化について調べてみました。’The Plan for React 18' というドキュメントから、こちらのディスカッションへのリンクが貼られています。この内容を読んでいってみましょう。
Batchingとはなにか?
Batching is when React groups multiple state updates into a single re-render for better performance.
Batchingは複数のステート更新が発生した時に、更新をまとめて1回の再描画で済ませることでより良いパフォーマンスを得る機能です。ステート更新のたびにrenderが呼ばれるのを回避するわけです。
バッチ化自体は17以前のReactでもサポートされていましたが、対象はエレメントのクリックのようなReactイベントのハンドラに限定されていました。
React 17以前のReactでのReactイベントハンドラ内のバッチ化
function App() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
function handleClick() {
setCount(c => c + 1); // ここではまだ再描画しない(renderを呼ばない)
setFlag(f => !f); // ここでもまだ再描画しない(renderを呼ばない)
// Reactは最後に1回だけレンダリングをします(それがバッチ化です)
}
return (
<div>
<button onClick={handleClick}>Next</button>
<h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
</div>
);
}
逆に、これまでバッチ化がサポートされていなかった場面は
・Promiseのコールバック内
・setTimeoutのコールバック内
・イベント・リスナーなどのコールバック内
などで、複数のステート更新が実行されるとそれぞれで再レンダリングが発生していました。
Automatic BatchingをONにするには?
React 18で新しくサポートされるようになるAPI ReactDOM.createRoot を使います。 ReactDOM.createRoot はこれまで使われてきた ReactDOM.render の代わりに使われるAPIで、createRootを使ってレンダリングされるとReactはすべてのステータス更新をバッチ処理するようになります。
// index.js
import ReactDOM from 'react-dom';
import App from 'App';
const container = document.getElementById('app');
// Create a root.
const root = ReactDOM.createRoot(container);
// Initial render: Render an element to the root.
root.render(<App tab="home" />);
// App.jsx
function App() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
function handleClick() {
fetchSomethingAsync()
.then(() => {
// React 18 以降では下記の処理をバッチにします
setCount(c => c + 1);
setFlag(f => !f);
// React はこの時点ではじめてレンダリングを実行します
});
}
return (
<div>
<button onClick={handleClick}>Next</button>
<h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
</div>
);
}
新しくバッチ化が適用されるようになるケース
タイマー処理
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// Reactは最後に1回だけレンダリングを実行します(バッチ化)
}, 1000);
Promise
fetch(/*...*/).then(() => {
setCount(c => c + 1);
setFlag(f => !f);
// Reactは最後に1回だけレンダリングを実行します(バッチ化)
})
ネイティブ・イベント・ハンドラー
elm.addEventListener('click', () => {
setCount(c => c + 1);
setFlag(f => !f);
// Reactは最後に1回だけレンダリングを実行します(バッチ化)
});
Reactのイベント・ハンドラだけでなく、ステート更新ならどんな場所でもバッチ化が自動で適用される訳ですね。これによって、個別のステート更新で再レンダリングされていた既存のコードのパフォーマンスが向上することが期待できます。
バッチ化によるステート更新の順序の保証
バッチ化については、このディスカッションをオープンしたDan AbramovがStackoverflowの解答で随分前に言及していました。
この内容を読むと、Automatic Batchingの導入によってReactのすべてのステート更新の順序はコードに書かれた順序で実行されることが保証されるようになるということのようです。
※ただし、React 18のAutomatic Batchingの説明にステートの更新順についての言及はありませんでした。これについては追って調査が必要ですね。
まとめ
React 18で導入されるAutomatic Batchingについて調べてみました。複数のステート更新に伴うレンダリングを一つに集約するバッチ化が、Reactのすべての場面で有効になります。これによってパフォーマンスが向上できるだけでなく、ステート更新そのものの信頼性を向上できることが分かりました。
React 18のリリースはまだ先ですが、楽しみな機能ですね。
・・・
Reactのステート更新とレンダリングの関係については、こちらのエントリも御覧ください。
おまけ: U-motion開発部はテクノロジーで農家さんに貢献したいエンジニアを募集中です!!
デザミス株式会社では、JavaScript/Reactでフロントエンド開発をしたいエンジニアと、バックエンド・エンジニアを募集中です。
こちらからお気軽に問い合わせてください。面接は無しでまずはカジュアル面談から、という方も大歓迎です。
フロントエンド
・React + TypeScriptを使った大規模なアプリケーション開発
・React Nativeを使ったネイティブ・アプリケーション開発
バックエンド
・大量のデータを扱うバックエンド開発(Rails、Python、AWS)
はじめてこのブログを読んだ方へ: U-motionとは?
U-motionは牛の首につけたセンサーを使って活動内容を記録、AIの力で健康状態を解析して畜産農家さんをサポートするモニタリング・システムです。
ユーザー様の声
プロダクト紹介
テレビ朝日夕方のニュース番組「スーパーニュースJ」でも紹介されました