フォームのボタンをクリックした時、なぜ近年のReactやNext.jsアプリは、重複クリック防止や連打制御でこうも面倒くさく、変な挙動にハマるのだろうか?
素のHTMLとJavaScriptであれば、ボタンを押した瞬間に `btn.disabled = true` と書くだけで何の問題もなく即座に動作していたはずだ。
なぜ現代のWeb開発は、こんな当たり前のことのために複雑なフック(`useState`, `useTransition`, `useCallback`)を駆使し、ボタンの更新タイミングに悩まされなければならないのか。
その原因は、Webのレンダリングにおける「真の二重行政」にある。
レンダリングの「三層構造」と、2と3の完璧な連携
Webアプリが画面に表示され、ユーザーが操作する時、そこには明確な「三層の構造」が存在している。
1. OSのレンダリング・イベントループ(Windows/macOS, DirectX/GPU, マウス・キーボード入力)
2. Chrome(ブラウザ)のレンダリング(Blinkエンジン, V8)
3. React / Next.js の JSレンダラー(仮想DOM, 独自ステート更新サイクル)
ここで最も重要な事実は、「2(Chrome)」と「1(OS)」は最初から完璧な密連携が取れているということだ。
OSの画面更新タイミング(V-Sync 60fps/120fps)や、マウスの物理的なクリックイベントに対して、ChromeのC++製エンジンは寸分の狂いもなくリアルタイムに追従し、グラフィックス層と完璧に噛み合って動作する。本来、これだけでWebは100%完成しており、何の問題もない。
OSのライフサイクルを無視して居座る「偽物レンダラー」
問題なのは、「3(React/Next.js)」が、2(Chrome)と1(OS)の完璧な連携ライフサイクルを完全に無視して、その上に勝手な「偽物レンダラー」を被せていることだ。
Reactは、OSやブラウザが元々持っているネイティブなイベント処理や描画のライフサイクルを無視し、JavaScript上に独自の「仮想DOM」と「非同期ステート更新サイクル」を作り上げた。
その結果、何が起きるか?
ユーザーがボタンをクリックした瞬間、OSとChromeなら即座に素直に処理が走るはずなのに、Reactの「偽物レンダラーの更新タイミング(再レンダリング待ち・非同期バッチ処理)」の都合にボタンが踊らされることになる。
- ボタンを押したのに `disabled` の反映が1フレーム遅れ、ダブルクリックで2重送信される。
- 重複クリック防止をしようとすると、JS側の再レンダリングサイクルと競合して変な癖や不具合が出る。
- フォームの入力やアニメーションが、JSの再レンダリング待ちで微小にガタつく。
本来、素のHTML+JSであれば、OSとブラウザのネイティブなイベントに乗っかるだけで、ミリ秒のズレもなく完璧に制御できていたはずなのだ。
まさに、「連携の取れているOS・ブラウザ(府)」の上に、独自ルールの「React偽物レンダラー(市)」が覆いかぶさり、ボタンやイベントを勝手なタイミングで踊らせている二重行政である。
流行りに騙されず「素直なWeb」を取り戻す
なぜ私たちは、OSとブラウザが最初から用意してくれている素直で美しいライフサイクルを捨て、JavaScript側の偽物レンダラーの都合に振り回されなければならないのか?
Vite.js を使ったシンプルで素直なWeb開発がこれほど快適なのは、こうした「偽物レンダラーによる過剰な二重行政」を削ぎ落とし、ブラウザとOSのネイティブなライフサイクルにそのまま乗っかってコードを動かせるからに他ならない。
`index.html` とブラウザ標準のJavaScriptであれば、ボタンは押した瞬間に素直に動き、余計なレンダリング待ちで踊らされることもない。
流行りの複雑なフレームワークが持ち込んできた「歪んだ二重構造」に気づき、OSとブラウザが本来持っている素直なモノづくりの原点に立ち返ること。
ボタン一つすら素直に動かせない重厚長大なシステムを笑い飛ばし、シンプルで爆速なWebを作る美学を、今こそ取り戻すべきだ。
