题干与适用场景
页面包含可调整大小的仪表盘卡片,卡片应根据自身容器而非视口决定布局。初版在 ResizeObserver 回调里同步读写样式,偶发性能下降和浏览器报告循环错误。请设计观察、测量、更新和清理流程。核心考察浏览器布局与组件生命周期,因此归入 frontend。
面试官考察点
面试官会看你是否区分 viewport media query 与 element observation,是否知道回调时机、盒模型、读写分离、循环保护和批量更新。还要处理 React 严格模式、多个元素、隐藏元素、服务端渲染和可访问性。
回答前需要澄清的问题
- 需要观察 content box、border box 还是 device-pixel content box?
- 组件更新的是 class、CSS 变量、Canvas 尺寸还是 DOM 几何?
- 是否会在回调里改变被观察元素或其祖先的尺寸?
- 页面有多少观察目标,更新是否需要节流或合并到下一帧?
- 组件如何在卸载、隐藏和 SSR 环境中创建与销毁 observer?
30 秒回答框架
“我只在客户端创建一个 observer,观察需要的盒模型,并在回调中先记录尺寸,再把 DOM 读取与写入分开。更新通过 CSS 类/变量或下一帧批量提交,避免回调直接改变自身尺寸形成反馈循环。每个元素保留最新尺寸,重复值不更新;卸载时 unobserve/disconnect。用长任务、布局次数、循环错误和 Strict Mode 重挂载测试。”
分步骤深入解答
ResizeObserver 观察元素内容或边框盒的变化,不依赖视口。选择 box 选项要与需求一致:布局断点通常使用 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。