題干與適用場景
頁面包含可調整大小的儀表板卡片,卡片應根據自身容器而非視口決定佈局。初版在 ResizeObserver 回呼中同步讀寫樣式,偶爾效能下降並出現迴圈錯誤。請設計觀察、測量、更新與清理流程。核心是瀏覽器佈局與元件生命週期,因此歸入 frontend。
面試官考察點
面試官會看你是否區分 viewport media query 與元素觀察,是否知道回呼時機、盒模型、讀寫分離、迴圈保護與批次更新。也要處理 React Strict Mode、多個元素、隱藏元素、伺服器端渲染與無障礙。
回答前需要釐清的問題
- 需要觀察 content box、border box 還是 device-pixel content box?
- 元件更新的是 class、CSS 變數、Canvas 尺寸還是 DOM 幾何?
- 回呼是否會改變被觀察元素或其祖先的尺寸?
- 頁面有多少觀察目標,更新是否需要節流或合併到下一幀?
- 元件如何在卸載、隱藏與 SSR 環境中建立和銷毀 observer?
30 秒回答框架
「我只在客戶端建立一個穩定的 observer,觀察需要的盒模型,回呼中先記錄尺寸,再把 DOM 讀取與寫入分開。更新透過 CSS class/變數或下一幀批次提交,避免回呼直接改變自身尺寸形成回饋迴圈。每個元素保留最新尺寸,重複值不更新;卸載時停止更新、unobserve 並 disconnect。用長任務、佈局次數、迴圈錯誤與 Strict Mode 重掛載測試。」
分步驟深入解答
ResizeObserver 觀察元素內容或邊框盒的變化,不依賴視口。盒模型要與需求一致:佈局斷點通常使用 content 或 border 尺寸,像素精確繪製才考慮 device-pixel content。不要在回呼裡反覆讀取會觸發強制佈局的屬性。
回呼階段先收集所有 entry 的尺寸,放入待處理集合,再統一計算斷點。將幾何讀取集中在同一批次,寫入 CSS 變數或 class;若寫入可能改變佈局,使用 requestAnimationFrame 延後,並在下一批次比較新舊值。
回饋迴圈來自「觀察尺寸 → 寫樣式 → 尺寸再次變化」,例如寬度改變 padding,padding 又改變寬度。設定斷點遲滯、固定容器約束,或只更新不會改變測量目標的屬性;若確實要調整尺寸,限制每幀一次並檢查是否收斂。瀏覽器會延後未收斂通知並回報錯誤,不應只忽略錯誤。
React 中在 useLayoutEffect 或合適的客戶端 effect 建立 observer,保存穩定 ref 與回呼,避免每次渲染重複訂閱。Strict Mode 可能執行 setup/cleanup 兩次,因此 cleanup 必須冪等;卸載時先停止更新,再 unobserve 和 disconnect。SSR 不要在伺服器存取 window 或建構器。
多個卡片可共用 observer,用 entry.target 對應元件狀態;但共用邊界要清楚,避免一張卡片阻塞整個回呼。拖曳時可只保存最新尺寸,在動畫幀合併更新,不要無條件 debounce 造成佈局延遲。
隱藏元素、display:none、字型載入與捲軸出現都會影響尺寸。對初始零尺寸提供可用預設佈局,等首個有效 entry 再切換;不要把測量結果直接寫進使用者可編輯內容。只需樣式斷點時優先使用 CSS container queries,ResizeObserver 適合 JS 計算、Canvas 或第三方元件協同。
驗證用 DevTools Performance 觀察佈局與腳本長任務,拖曳改變容器、切換字型、隱藏/顯示、旋轉螢幕與批量掛載。斷言每次尺寸變化只觸發必要更新,沒有持續迴圈、重複 observer、卸載後 setState 或顯著佈局抖動,並測試鍵盤與螢幕閱讀器可用性。
高品質示範回答
「我會在客戶端建立穩定的 ResizeObserver,按需求觀察 content 或 border box。回呼只收集最新 entry 和計算斷點,不立即反覆讀寫 DOM;寫入 CSS 變數或 class,必要時在下一幀批次提交。若寫入會改變觀察目標,就加斷點遲滯、固定約束和收斂檢查,避免回饋迴圈。
React cleanup 要冪等,卸載時停止更新並 disconnect,SSR 不建構 observer。多個卡片可共用 observer 但按 target 分發。驗證時拖曳、字型載入、隱藏顯示和 Strict Mode 重掛載,檢查佈局次數、長任務、迴圈錯誤、重複訂閱和無障礙操作。」
常見錯誤
- 把 ResizeObserver 當 viewport media query → 元件在不同容器失效 → 觀察元素自身尺寸。
- 回呼中同步讀寫大量 DOM → 強制佈局和抖動 → 批次讀取,下一幀寫入。
- 修改觀察目標卻不做收斂 → 回饋迴圈 → 斷點遲滯、約束與迴圈檢測。
- 每次 render 新建 observer → 重複回呼與洩漏 → 穩定 ref、依賴和 cleanup。
- 只呼叫 unobserve 不清理回呼狀態 → 卸載後仍更新 → 先標記 inactive,再解除觀察並斷開。
- 對零尺寸直接套最大佈局 → 首屏跳動 → 提供預設狀態,等待有效 entry。
- 所有問題都用 JS 計算 → 複雜且遲緩 → 能用 container query 就交給 CSS。
- 只測正常拖曳 → 字型、隱藏和 Strict Mode 出問題 → 覆蓋生命週期與資源變化。
追問及應對
追問一:ResizeObserver 回呼何時執行?
瀏覽器在佈局後批次交付尺寸變化通知,未收斂的變化可能延後到下一幀並觸發迴圈錯誤。實作要避免在回呼中無界改變佈局。
追問二:為什麼不用 window.resize?
window.resize 只反映視口變化,無法捕捉卡片因網格、側欄或字型造成的自身尺寸變化;ResizeObserver 直接觀察元素。
追問三:如何避免讀寫互相觸發?
先集中讀取 entry 提供的尺寸,計算結果後批次寫 CSS 變數或 class,必要時使用 requestAnimationFrame,並比較新舊值。
追問四:多個元素應該各建一個 observer 嗎?
數量少且生命週期獨立時各自建立簡單;大量目標可共用 observer,用 target 對應狀態,但要限制單次回呼工作量。
追問五:元素 display:none 後恢復怎麼辦?
允許零尺寸作為暫態,恢復後等待有效 entry,再套用佈局;不要把零尺寸當永久斷點,也不要在隱藏期間迴圈寫樣式。
追問六:React Strict Mode 為什麼暴露問題?
開發模式可能重複執行 effect 的 setup/cleanup,用來發現副作用不對稱。cleanup 必須能重複執行且不留下 observer 或更新回呼。
追問七:何時只用 CSS container query?
只需根據容器斷點切換樣式時優先 CSS;需要 Canvas、第三方 API、複雜測量或把尺寸用於業務計算時再使用 ResizeObserver。