告警 Detected multiple renderers concurrently rendering the same context provider

使用 Arco Design 点击按钮弹框或者弹出Message提示框出现 Warning: Detected multiple renderers concurrently rendering the same context provider. This is currently unsupported. 的警告,分析原因通常是因为在同一应用中存在多个 React 渲染器(React Renderer)并发渲染同一个 Context Provider。例如

  1. 多个 React 根节点 (React Root) 使用了相同的 Context Provider。
  2. 在测试环境或某些特殊场景下,同一个 Context 被多个独立的 React 实例渲染。
  3. 混合使用不同的 React 版本或渲染方式(如 ReactDOM 和 React Native)。

Arco Design 的某些组件(例如 Modal、Notification、Message 等)依赖全局 Context(如 ConfigProvider),当这些 Context 被多个渲染器同时操作时,就会触发此警告。


解决方法

1. 确保只有一个 React Root

检查你的应用是否创建了多个 React Root(通过 ReactDOM.rendercreateRoot)。如果有多个根节点,确保它们共享同一个 Context Provider。尤其是在使用了组件联邦的架构下,很容易出现此问题。

jsx 复制代码
// 不推荐:多个独立的 Root
ReactDOM.render(<App />, document.getElementById('root1'));
ReactDOM.render(<App />, document.getElementById('root2'));

// 推荐:只有一个 Root
ReactDOM.render(
  <ConfigProvider>
    <App />
  </ConfigProvider>,
  document.getElementById('root')
);

如果你使用 React 18 的 createRoot

jsx 复制代码
import { createRoot } from 'react-dom/client';
import { ConfigProvider } from '@arco-design/web-react';

const root = createRoot(document.getElementById('root'));
root.render(
  <ConfigProvider>
    <App />
  </ConfigProvider>
);

2. 检查 Arco 的全局组件使用方式

Arco Design 的 Modal、Notification 等组件会通过 Context 与全局状态交互。如果你手动调用这些组件的静态方法(例如 Modal.confirm),确保它们运行在同一个 React Context 树中。

错误示例:

jsx 复制代码
import { Modal } from '@arco-design/web-react';

// 在 React 树之外直接调用
Modal.confirm({
  title: '提示',
  content: '这是一个模态框',
});

正确示例: 确保 Modal 被包裹在 ConfigProvider 中:如果全局入口已经加了ConfigProvicer了还出现此警告,可以尝试在子组件继续包裹,如下

jsx 复制代码
import { ConfigProvider, Modal } from '@arco-design/web-react';

function App() {
  const showModal = () => {
    Modal.confirm({
      title: '提示',
      content: '这是一个模态框',
    });
  };

  return (
    <ConfigProvider>
      <button onClick={showModal}>打开 Modal</button>
    </ConfigProvider>
  );
}

3. 避免在测试环境中重复渲染 Context

在 Jest 或其他测试框架中,如果你在多个测试用例中重复渲染包含 ConfigProvider 的组件,可能会触发此警告。解决方法是确保每个测试用例独立清理渲染结果。

jsx 复制代码
import { render, screen } from '@testing-library/react';
import { ConfigProvider } from '@arco-design/web-react';

describe('MyComponent', () => {
  afterEach(() => {
    // 清理渲染
    document.body.innerHTML = '';
  });

  it('renders correctly', () => {
    render(
      <ConfigProvider>
        <MyComponent />
      </ConfigProvider>
    );
    expect(screen.getByText('test')).toBeInTheDocument();
  });
});

或者使用 @testing-library/reactcleanup 功能:

jsx 复制代码
import { render, screen, cleanup } from '@testing-library/react';
import { ConfigProvider } from '@arco-design/web-react';

afterEach(cleanup);

it('renders correctly', () => {
  render(
    <ConfigProvider>
      <MyComponent />
    </ConfigProvider>
  );
  expect(screen.getByText('test')).toBeInTheDocument();
});

4. 检查是否有第三方库导致的多重渲染

某些第三方库可能在独立的 React 实例中渲染组件,并与 Arco Design 的 Context 发生冲突。检查你的依赖中是否有类似情况,并尝试将这些库的渲染合并到主 React 树中。


排查步骤

  1. 定位问题来源 :在警告信息中查看完整的 Error Component Stack,找到触发警告的具体组件。
  2. 检查 Context 使用 :确认是否有多个 ConfigProvider 实例或独立渲染的 Arco 组件。
  3. 调试渲染树:使用 React Developer Tools 检查是否有多个独立的 React 树。
  4. 简化代码:逐步移除部分代码,定位触发警告的最小复现案例。

示例:完整修复后的代码

以下是一个典型的列表 + Modal 的实现:

jsx 复制代码
import React, { useState } from 'react';
import { createRoot } from 'react-dom/client';
import { ConfigProvider, Modal, Form, Input, Button, Table } from '@arco-design/web-react';

const App = () => {
  const [dataSource, setDataSource] = useState([]);
  const [isModalVisible, setIsModalVisible] = useState(false);
  const [editingItem, setEditingItem] = useState(null);
  const [form] = Form.useForm();

  const columns = [
    { title: '名称', dataIndex: 'name' },
    {
      title: '操作',
      render: (_, record) => (
        <Button onClick={() => {
          setEditingItem(record);
          form.setFieldsValue(record);
          setIsModalVisible(true);
        }}>编辑</Button>
      ),
    },
  ];

  const handleSubmit = (values) => {
    if (editingItem) {
      setDataSource(dataSource.map(item => 
        item.id === editingItem.id ? { ...item, ...values } : item
      ));
    } else {
      setDataSource([...dataSource, { id: Date.now(), ...values }]);
    }
    setIsModalVisible(false);
    form.resetFields();
    setEditingItem(null);
  };

  return (
    <ConfigProvider>
      <Button onClick={() => setIsModalVisible(true)} style={{ marginBottom: 16 }}>
        新增
      </Button>
      <Table dataSource={dataSource} columns={columns} rowKey="id" />
      <Modal
        title={editingItem ? '编辑' : '新增'}
        visible={isModalVisible}
        onCancel={() => {
          setIsModalVisible(false);
          form.resetFields();
          setEditingItem(null);
        }}
        footer={null}
      >
        <Form form={form} onFinish={handleSubmit} layout="vertical">
          <Form.Item name="name" label="名称" rules={[{ required: true }]}>
            <Input />
          </Form.Item>
          <Form.Item>
            <Button type="primary" htmlType="submit">
              提交
            </Button>
          </Form.Item>
        </Form>
      </Modal>
    </ConfigProvider>
  );
};

const root = createRoot(document.getElementById('root'));
root.render(<App />);

以上全文。

相关推荐
满栀58544 分钟前
vue动态路由效果
前端·javascript·vue.js·前端框架·vue
sunly_2 小时前
React useActionState 的用法详解
前端·javascript·react.js
汉堡大王95273 小时前
前端工程师 TypeScript 上手指南:一条清晰的从入门到精通路线
前端·javascript·react.js
wear工程师3 小时前
防抖为什么会失效?React 面试里的闭包和定时器
javascript·react.js
张元清4 小时前
React useEventSource Hook:自带断线重连的 Server-Sent Events (2026)
javascript·react.js
光影少年4 小时前
RN 网络请求:Fetch / Axios 封装、超时、拦截器
前端·react native·react.js
gis开发之家8 小时前
《Vue3 从入门到大神50篇》Vue3 源码详解(二十):生命周期钩子源码解析 —— onMounted / onUpdated 如何实现?
前端·javascript·前端框架·vue3·vue3源码
word8 小时前
从零接入 MCP:把任意工具变成 AI 的能力(协议级实践)
人工智能·前端框架
何时梦醒9 小时前
第三篇:React 组件化开发 — 函数即组件的哲学与实践
react.js·面试·typescript
console.log('npc')9 小时前
Mock 服务设计指南
前端框架·mock·react·visual studio·项目架构