您正在对之前已传递给 createRoot() 的容器调用 ReactDOMClient.createRoot()

问题描述 投票:0回答:8

我正在使用 React 18 并在 3 个不同级别使用

ReactDOM.render
。第一级位于根级,我很清楚并使用以下代码替换了它:

import { createRoot } from 'react-dom/client';
    
const root = createRoot(domElement);
root.render(reactElement);

对于其他两个级别(根的子级),我想在指定的 DOM 元素中渲染某个组件。如果我正在使用:

import { createRoot } from 'react-dom/client';

const root = createRoot(childDomElement);
root.render(reactElement);

我收到以下警告:

您正在对之前已传递给 createRoot() 的容器调用 ReactDOMClient.createRoot() 。相反,如果您想更新它,请在现有根上调用 root.render() 。

在特定 DOM 元素中渲染组件的正确方法是什么?

reactjs react-dom
8个回答
33
投票

我也遇到过这种情况。对我来说,这是因为

DOMContentLoaded
回调触发了两次。

我的修复只是确保容器仅渲染一次。

let container = null;

document.addEventListener('DOMContentLoaded', function(event) {
  if (!container) {
    container = document.getElementById('root1') as HTMLElement;
    const root = createRoot(container)
    root.render(
      <React.StrictMode>
        <h1>ZOO</h1>
      </React.StrictMode>
    );
  }
});

8
投票

您可能从入口点文件导入某些内容,导致入口点文件以某种方式运行两次。我遇到了同样的问题,并通过确保我没有从入口点文件导入任何内容来解决它。


2
投票

可能还有其他人在使用

'react-router-dom'
包时遇到与我相同的错误。

删除

<App />
(或主组件)组件,并在
<RouterProvider />
函数内调用
.render()
组件。像这样:

ReactDOM.createRoot(document.getElementById('root')).render(<App />)

应替换为

ReactDOM.createRoot(document.getElementById('root')).render(<RouterProvider router={mainRouter} />)

当您在同一页面上同时调用

<RouterProvider>
.createRoot
时,就会出现问题。要解决此问题,您应该导入并调用
<RouterProvider>
<App>
组件来解决此问题,而不是直接渲染
<RouterProvider>
组件。

RouterProvider 文档


0
投票

触发此警告的典型方法是在同一根上多次调用

createRoot

ReactDOM.createRoot(document.querySelector("#app"))
  .render(<p>A</p>);
ReactDOM.createRoot(document.querySelector("#app"))
  .render(<p>B</p>);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="app"></div>

您可以看到

B
渲染覆盖了
A
渲染。

如果你想运行两个不同的 React 应用程序,请使用不同的根:

ReactDOM.createRoot(document.querySelector("#app-a"))
  .render(<p>A</p>);
ReactDOM.createRoot(document.querySelector("#app-b"))
  .render(<p>B</p>);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="app-a"></div>
<div id="app-b"></div>

否则,如果您尝试在同一应用程序中渲染不同的组件,请使用单个根组件渲染一次,并在根的子树内渲染子组件。这是常见的情况;大多数页面都使用一个 React 应用程序。

在大多数情况下,

createRoot
充当整个 React 应用程序的一次性设置,在用户访问页面期间持续存在。渲染条件位于组件树内部,并且要渲染的元素在组件中指定为 JSX。

例如:

const A = () => <p>A</p>;
const B = () => <p>B</p>;
const App = () => <React.Fragment><A /><B /></React.Fragment>;

ReactDOM.createRoot(document.querySelector("#app"))
  .render(<App />);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="app"></div>

如果出于某种原因,您想多次渲染该根,只要您限制自己为每个根调用一次

createRoot
即可:

const root = ReactDOM.createRoot(document.querySelector("#app"));
root.render(<p>A</p>);

// silly example
setTimeout(() => root.render(<p>B</p>), 2000);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="app"></div>

另一种可能性是您正在寻找一个门户,可以让您逃离 React DOM 树并在其他地方渲染组件。不过,如上所述,仍然有一个

createRoot
和一个
render
,我们使用门户将组件放入特定元素中。

例如:

const A = () => <p>A</p>;
const B = () => ReactDOM.createPortal(
  <p>B</p>,
  document.querySelector("#portal")
);
const App = () => <React.Fragment><A /><B /></React.Fragment>;

ReactDOM.createRoot(document.querySelector("#app"))
  .render(<App />);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="app"></div>
<div id="portal"></div>

门户经常出现在模态中。


为了完整起见,触发此问题的另一种不常见方法是包含 Babel 两次,这可能发生在具有

babel: true
以及导入的 Stack Snippet 中:

ReactDOM.createRoot(document.querySelector("#app"))
  .render(<p>test</p>);
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="app"></div>

这或多或少与以下相同:

<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="app"></div>
<script type="text/babel">
ReactDOM.createRoot(document.querySelector("#app"))
  .render(<p>test</p>);
</script>


0
投票

我遇到了这个问题,我正在使用 Rollup 捆绑一个复杂的 NPM 包,该包与我们的 Storybook 应用程序和 Create-React-App 演示页面位于同一目录中。

事实证明,我们正在捆绑 Create-React-App index.js 文件,其中已经包含 createRoot() 了。这意味着它试图将包中的 React 应用程序与我们的 React 应用程序一起附加。

我通过不捆绑我们用来演示组件的 index.js 文件来解决这个问题,并制作了一个不包含 createRoot() 的“package-index.js”文件,专门用于该包。

希望这对某人有帮助:)


0
投票

我有类似的情况,我必须在当前组件中单击按钮时渲染另一个组件。我通过以下代码成功实现了这一点。

import React  from "react";
import  ReactDOM  from "react-dom/client";
import reportWebVitals from "./reportWebVitals"; 

class Lifecycle extends React.Component{ 
  showReports=()=>{  
    root.render(<Reports></Reports>);    
  }
  render() {
    return (
      <div>
        <h2>Welcome to Component Lifecycle..</h2>        
        <button onClick={this.showReports}>Show Reports</button>
        </div>
      );
    }
}

class Report extends React.Component{ 
  
  render() {
    return (
      <div>
        <h2>Welcome to Report Component..</h2>                
        </div>
      );
    }
}


const element=<Lifecycle></Lifecycle>;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(element);
reportWebVitals();

-1
投票

我通过一个愚蠢的错误解决了这个问题,旧参数仍然存在:

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

最后一个不应该在那里

document.getElementById('root'),


-4
投票

答案就在警告本身中。

您正在一个 container 上调用 ReactDOMClient.createRoot() ,该容器具有 已经被传递给 createRoot() before

我最后出现警告的根本原因是同一个 DOM 元素多次用于创建根。

为了解决这个问题,需要确保

createRoot
仅在 一个 DOM 元素上调用一次,之后
root.render(reactElement);
可以用于更新,并且
createRoot
不应该被使用。

© www.soinside.com 2019 - 2024. All rights reserved.