こんにちは!Reactの学習、楽しく進めていますか?
「画面の見た目をパパッと変えたいのに、なぜかワンテンポ遅れる気がする……」「ボタンを押した直後に、どうしてもDOMの大きさを測りたいのにうまくいかない!」
そんな、Reactの「ちょっとおせっかいな親切心」に頭を悩ませたことはありませんか?
今回は、そんなReactの「バッチ処理(まとめて処理する機能)」の裏側をのぞき、どうしても今すぐDOMを更新させたいときの秘密兵器`flushSync`について、身近なたとえ話を交えながら優しく紐解いていきますね。大丈夫、ひとつずつ見ていけば絶対に理解できますから、肩の力を抜いていきましょう!
—
1. Reactの「おまとめ便(バッチ処理)」ってなに?
まずは、Reactが普段裏側でどんなことをしているのか、イメージしてみましょう。
例えば、あなたがネット通販で文房具をいくつか買ったとします。
- 1個目に「消しゴム」を買う。
- 2個目に「シャー芯」を買う。
- 3個目に「ノート」を買う。
このとき、お店側が「消しゴム1個発送しました!」「シャー芯1個発送しました!」「ノート1個発送しました!」と、わざわざ3回に分けて別々のダンボールで送ってきたらどうでしょう? 配達員さんも大変ですし、ダンボールのゴミも増えてエコじゃないですよね。
普通のお店なら、「あ、同じ人が短時間にいくつか買ったから、ひとつのダンボールにまとめて(バッチで)発送しちゃおう!」と気遣ってくれます。
Reactの「状態更新(`useState`)」もこれと全く同じです。
ボタンを1回押した中で、いくつかのデータを更新したり、画面を再描画(レンダリング)させたりするとき、Reactはパフォーマンスを落とさないために、「ちょっと待て、全部の変更が出揃ってから、一度にまとめて画面をキレイに塗り替えるね!」と気を利かせてくれます。これがバッチ処理です。
普段はこの「おまとめ便」のおかげで、アプリがサクサク軽快に動くのですが……。
—
2. まとめてもらっては困る!そんな時もあるんです
「おまとめ便」は普段は大活躍なのですが、ごく稀にこんなシチュエーションに出会います。
> 「ねえ、お願いだから、この1個目の注文分だけでもいいから、今すぐ、今この瞬間に発送(DOMの更新)してよ! その結果のダンボールの大きさをメジャーで測りたいんだから!」
例えば、以下のようなケースです。
1. リストに新しい項目を追加する。
2. 追加された直後のリストの一番下に、画面をググッとスクロールさせたい。
このとき、Reactの「おまとめ便」に頼っていると、「じゃあまとめて後でスクロール位置を調整するね」となり、DOM(実際の画面の要素)がまだ描画し終わっていないうちにスクロール処理が走ってしまい、「あれ? 一番下にスクロールできない……?」というバグ(罠)にハマってしまいます。
そんな、「ちょっと待って、今すぐ同期的に画面を更新して!」とReactのおまとめ便を一時的にストップさせる魔法の呪文が、`flushSync` なのです。
—
3. `flushSync`の使い方と優しく学べるサンプルコード
百聞は一見にしかず。実際のコードを見てみましょう。
ここでは、「アイテムを追加した直後に、スクロール位置を一番下にピタッと合わせる」というよくある機能を例にします。
import React, { useState, useRef } from ‘react’;
// 1. react-dom から flushSync をインポートします
import { flushSync } from ‘react-dom’;
export default function ScrollApp() {
const [items, setItems] = useState([‘りんご’, ‘みかん’, ‘ぶどう’]);
const listRef = useRef(null);
const handleAddItem = () => {
// 新しいアイテムを作るよ
const nextItem = `アイテム ${items.length + 1}`;
// 通常の setState だと、この後のコードが実行される時点では
// まだ画面のDOMは更新されていません(おまとめ便の中)。
// 2. flushSync で囲むことで、「今すぐ状態を更新して、DOMの反映まで一気にやりきって!」と指示します
flushSync(() => {
setItems((prevItems) => […prevItems, nextItem]);
});
// ここに到達した瞬間、Reactは「さっきの変更」をすでに画面上のDOMに反映し終えています!
// だから、安心して今現在の正確な高さを測ることができるのです。
if (listRef.current) {
listRef.current.scrollTop = listRef.current.scrollHeight;
console.log(‘スクロール位置を一番下に強制移動しました!’);
}
};
return (
flushSync 体験アプリ
{/ スクロールが発生するボックス /}
{items.map((item, index) => (
))}
);
}
このコードでは、`flushSync` のおかげで「リストに要素が追加されてからスクロールさせる」という一連の流れが、タイムラグなしで確実に実現できています。
—
4. チーフアーキテクトからの大切なアドバイス(注意点)
ここまで読んで、「なんだ、バグったときは全部 `flushSync` で囲んじゃえば万事解決じゃん!」と思ったそこのあなた。ちょっと待ってくださいね。
実務の世界では、`flushSync` は劇薬(諸刃の剣)として扱われます。
なぜなら、Reactがせっかく裏側でスマートにまとめてくれている「おまとめ便(パフォーマンス最適化)」を、人間が無理やり「いや、今すぐ全部バラバラに処理しろ!」と強制的に止めることになるため、多用するとアプリ全体の動作が重くなったり、レンダリングの回数が増えすぎてカクついたりする原因になるからです。
使うときの心構え
- 基本は通常の `useState`(おまとめ便)にまかせること。
- DOMのサイズ測定や、ブラウザのAPI(フォーカス制御やアニメーションなど)とどうしても同期させなきゃいけない特別な瞬間だけに、そっと使うこと。
もし「あれ、なんか画面の動きがカクカクするぞ?」と感じたら、コードのどこかで無駄な `flushSync` を使っていないか見直してみると、すんなり解決することがよくあります。
—
まとめ
- Reactのバッチ処理は、複数の状態変更をまとめて処理してパフォーマンスを保つ「おまとめ便」のようなもの。
- `flushSync` は、そのおまとめ便を一時的に無効化し、「今すぐDOMを更新してくれ!」と同期的に強制するための仕組み。
- 追加した直後のDOMの大きさを測ったり、スクロール位置を調整したりする特別な場面で大活躍する。
- ただし、パフォーマンス低下の原因になることもあるので、ここぞという時だけ優しく使ってあげよう。
最初は難しく感じるかもしれませんが、実際に手を動かしてコンソールログを眺めてみると、「なるほど、ここでDOMが作られているんだな」と感覚がつかめてきますよ。焦らず、一歩ずつ自分のペースで進んでいきましょうね。あなたのReactライフを応援しています!

コメント