ドラッグ中にsetStateを避けて直接DOM更新する要素は、他要素との接続線も同じ手法で同期させる必要がある
Reactでキャンバス型UI(マインドマップ・フローチャート等)を実装する際、パフォーマンスのためドラッグ中はpointermoveごとのsetStateを避け、対象要素のel.style.left/topを直接書き換える手法はよく使われる。この手法自体は正しいが、その要素と接続している他の視覚要素(SVGのエッジ・コネクタ・矢印など、Reactのpropsから座標を再計算して描画されるもの)を見落としやすい。ドラッグ中のノード自体は滑らかに動くのに、接続線だけが更新前の位置のまま取り残される、という部分的な desync が起きる。
適用条件: 1つの要素が「setStateを介さない直接DOM更新」で高頻度に動く一方、その要素の座標に依存する別の描画要素(エッジ・接続線・追従ラベル等)がReactの通常のprops経由レンダリングのままになっている場合。
対処方法: 依存先の描画要素も同じ「直接DOM更新」の経路に乗せる。具体的には、(1) 描画に使うパス計算式・アンカー座標計算をReactレンダリングと直接DOM更新の両方から呼べる純粋関数として共通化する、(2) 対象のSVG要素等にidや data属性を振っておき、ドラッグハンドラからquerySelectorで直接取得して属性(例: パスのd属性)を書き換える、(3) 更新はpointermoveごとに同期実行せず、requestAnimationFrameで1フレーム1回に間引く。間引きの際は「予約時点の値」ではなく「発火時点の最新値」を使う(pendingな値を保持するrefと、rAF予約済みフラグを分離し、複数回のpointermoveが来ても最後の値だけが反映されるようにする)。ドラッグ終了時(pointerup)に、確定値をstateへ反映する一度だけの再描画を行う。
確認方法: ドラッグをゆっくり複数ステップに分けて実行し、各ステップでスクリーンショットを撮って、ドラッグ対象の要素だけでなく依存先の描画要素(線・接続点など)も追従して更新されていることを目視確認する。
教訓: 「パフォーマンスのためにsetStateを避けて直接DOM操作する」という最適化は、その要素単体では正しくても、依存グラフ全体で見ると「一部だけ最適化して他が取り残される」新しい不整合を生みやすい。ある要素をReactの再レンダー経路から外すときは、それに依存する描画要素も同じ経路に揃えるところまでが最適化の範囲。