Skip to content

ResizeObserver 循环未完成通知的解决方案

问题描述

在 React 应用开发过程中,您可能会在浏览器控制台看到类似这样的错误提示:

ResizeObserver loop completed with undelivered notifications

该错误通常出现在以下场景:

  • 在模态框关闭或组件卸载时
  • 升级技术栈(如 react-router v5 → v6)后出现
  • 主要出现在开发环境(webpack-dev-server 运行时)
  • 可能与表单元素、响应式组件或浏览器扩展有关

此错误表明浏览器无法及时处理所有ResizeObserver通知,通常由于快速连续触发的大量布局变化导致循环超限。虽然该错误在生产环境中可能仅会打印到控制台,但可能隐藏着性能问题和潜在布局问题。

常见原因

综合社区经验和实践案例,主要诱因包括:

  1. 浏览器扩展干扰:LastPass 等密码管理工具对动态创建的表单元素过度响应
  2. UI 组件行为:Material-UI、Ant Design 等库中的自适应组件(如带multiline属性的 TextField
  3. 无限布局循环:组件的尺寸调整触发了连续的回流(reflow)过程
  4. 快速布局变化:DOM 结构复杂时出现的密集尺寸变化事件
  5. 第三方库兼容问题:旧版库(如 Bootstrap v3)与新路由机制冲突

解决方案

🛠️ 1. 排除浏览器扩展干扰

最便捷的解决方案是测试浏览器扩展的影响:

bash
# 操作步骤:
1. 进入无痕浏览模式
2. 禁用 LastPass 等扩展
3. 检查错误是否消失

如果问题扩展相关,可在代码层面规避:

jsx
// 对于 Material-UI TextField
<TextField
  label="Description"
  variant="outlined"
  // 避免过宽触发扩展干预
  sx={{ 
    minWidth: "200px",
    "& textarea": { minHeight: "100px" } 
  }}
/>

📐 2. 调整 UI 组件配置

使用 Material-UI、Ant Design 或类似 UI 库时,尝试:

jsx
// Material-UI TextField 解决方案
<TextField
  multiline
  rows={4} // 指定固定行数
  // 移除 minRows/maxRows 避免动态变化
  sx={{ 
    width: "98%", // 避免100%宽度引发问题
    "& .MuiInputBase-root": { minHeight: "120px" }
  }}
/>

// Ant Design 解决方案
<Table scroll={{ x: "auto" }} /> // 移除 x:"max-content"

优化实践

  • 优先指定固定高度/宽度而非百分比
  • 避免 fullWidthmultiline 同时使用
  • 对于动态内容,设置合理的 minHeight/minWidth

⏱️ 3. 使用防抖优化 ResizeObserver

当自定义使用 ResizeObserver 时,务必添加防抖逻辑:

jsx
import { useState, useEffect, useRef } from 'react';

const useResponsiveElement = (ref) => {
  const timerRef = useRef(null);
  
  useEffect(() => {
    const observer = new ResizeObserver((entries) => {
      clearTimeout(timerRef.current);
      
      // 添加100ms防抖延迟
      timerRef.current = setTimeout(() => {
        const entry = entries[0];
        if (entry & entry.target) {
          const { width, height } = entry.contentRect;
          // 执行实际业务逻辑...
        }
      }, 100);
    });
    
    if (ref.current) observer.observe(ref.current);
    
    return () => observer.disconnect();
  }, [ref]);
};

⚙️ 4. Webpack 开发环境配置

仅针对开发环境错误提示,配置 webpack 过滤器:

js
module.exports = {
  devServer: {
    client: {
      overlay: {
        runtimeErrors: (error) => {
          // 过滤特定错误不显示覆盖层
          if (
            error.message.includes(
              "ResizeObserver loop completed"
            )
          ) {
            console.error(error); // 保持控制台输出
            return false;
          }
          return true;
        }
      }
    }
  }
};

🛡️ 5. 安全使用自定义 ResizeObserver Hook

创建自定义 Hook 时,必须包含边界检查:

jsx
const useSafeResizeObserver = (ref) => {
  const [dimensions, setDimensions] = useState({ width: 0, height: 0 });

  useEffect(() => {
    if (!ref.current) return;
    
    const observer = new ResizeObserver((entries) => {
      if (!entries.length) return;
      
      const entry = entries[0];
      window.requestAnimationFrame(() => {
        const { inlineSize, blockSize } = entry.contentBoxSize[0];
        setDimensions({ width: inlineSize, height: blockSize });
      });
    });
    
    observer.observe(ref.current);
    return () => observer.disconnect();
  }, [ref]);
  
  return dimensions;
};

不推荐的临时方案

虽然有些答案建议直接隐藏错误,但应作为最后手段

js
// ⚠️ 不推荐的全局错误抑制
window.addEventListener('error', (e) => {
  if (e.message.includes('ResizeObserver')) e.stopImmediatePropagation();
});

此方案会屏蔽合法错误,仅应在确保是误报时使用

总结

当遇到ResizeObserver loop completed with undelivered notifications错误时,按优先级排查:

  1. 禁用浏览器扩展测试是否为干扰源
  2. 优化自适应组件,避免冲突属性组合
  3. 添加防抖逻辑到 ResizeObserver 监听器
  4. 开发环境配置 webpack 错误过滤
  5. 重构复杂布局拆解相互依赖的尺寸调整

核心解决思路是打破无限布局循环,确保尺寸调整不会链式触发新调整动作。生产环境中,此错误通常不会导致 UI 崩溃,但建议在开发阶段根除以避免潜在性能问题。