拖曳與平移的暫存狀態放 ref 而非 state

互動進行中需要紀錄「起始點、基準偏移、目前是哪個物件被拖」這類資料,這些資料不需要觸發畫面重繪,放 useRef 而非 useState。每秒幾十次的 pointermove 才不會炸 re-render。

做法

const panStateRef  = useRef<{ startX: number; startY: number; baseOffsetX: number; baseOffsetY: number } | null>(null)
const dragStateRef = useRef<{ id: string; startWorld: Point; baseX: number; baseY: number } | null>(null)
const drawStateRef = useRef<{ kind: 'pen' | 'rect' | 'ellipse'; startWorld: Point } | null>(null)
 
const onPointerDown = (e) => {
  panStateRef.current = { startX: e.clientX, ..., baseOffsetX: viewport.offsetX, ... }
}
 
const onPointerMove = (e) => {
  if (panStateRef.current) {
    const { startX, baseOffsetX } = panStateRef.current
    setViewport(vp => ({ ...vp, offsetX: baseOffsetX + (e.clientX - startX) }))
  }
}
 
const onPointerUp = () => {
  panStateRef.current = null
}

判斷標準:這份資料變了,畫面要重畫嗎?

  • 要重畫useState(如 viewport、items、目前選取的 id)
  • 不重畫,只是過程紀錄useRef(如 startX、基準偏移、目前是哪個物件正在被拖)

把所有東西都塞 useState 是新手最常見的效能問題:每秒 60+ 次 move event,每次都 setState → 每次都 re-render 整個畫布。

為什麼「基準值」要在 down 時存起來

// 錯:每次 move 都用 viewport.offsetX 當基準 → 結果是位移會累積/抖動
setViewport(vp => ({ ...vp, offsetX: vp.offsetX + (e.clientX - lastX) }))
 
// 對:down 時鎖定基準,move 時算「相對於基準的總位移」
setViewport(vp => ({ ...vp, offsetX: baseOffsetX + (e.clientX - startX) }))

後者的 e.clientX - startX 永遠是「從按下到現在的總位移」,再加到「按下時的基準」,數學乾淨、不會累積誤差。

相關