[译]我的 React 组件会渲染两次,我快疯了

9,935 阅读5分钟

我的 React 组件会渲染两次,我快疯了

很多使用现代 React 的前端开发者,时常遇到组件渲染两次的情况。这害得他们都快把自己薅秃了。

另外一些人注意到了这个行为,但是他们觉得这是 React 运行的原理。又有些人会在 React 官方 repository 上发起工单,把这当做一个 bug 上报。

所以开发者社区中肯定对此存在着一些困惑。😬

这些事情发生的原因是 React.StrictMode

我们来看一些真实的例子,复现一下这种情况,然后研究它为什么会发生吧。

函数组件的例子

我们可以从运行一个新的 CRA 安装命令开始:

npx create-react-app my-app && cd my-app

我们稍稍改动 App.js ,增加一个超级简单的 console.log 语句:

function App() {
  console.log('I render 😁');

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
      </header>
    </div>
  );
}

现在我们可以通过 yarn start 启动我们的应用,并且在浏览器中打开 http://localhost:3000:

我的 React 组件会渲染两次,我快疯了

嗯,I render 😁 语句只输出了一次,所以我们不能通过一个超级简单的函数组件实现渲染两次。

带状态的函数组件例子

如果我们使用一个 React hook,然后在函数组件中加入一些状态语句会发生什么?

function App() {
  const [clicks, setClicks] = React.useState(0);

  console.log('I render 😁');

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />

        <button onClick={() => setClicks(clicks => clicks + 1)}>
            Clicks: {clicks}
        </button>
      </header>
    </div>
  );
}

我们再看一下浏览器:

我的 React 组件会渲染两次,我快疯了

实现了!它首先渲染了两次,然后每当我们点击加入的按钮时,都渲染两次。

显然,React.useState 影响了组件在重复渲染方面的行为。

生产环境中带状态的函数组件例子

生产环境 bundle 又如何呢?为了检查这一点,我们需要首先构建自己的应用,然后在 3000 端口中通过 serve 之类的包使用它:

yarn build && npx serve build -l 3000

在浏览器中再次打开 http://localhost:3000

我的 React 组件会渲染两次,我快疯了

哎呦!调试语句在开始时打印了一次,并且在我们每次点击按钮时打印一次。

如我们所见,渲染两次的行为在生产环境中肯定不能复现,尽管我们对于 React.useState 的用法是完全一致的。

为什么会发生这种事?

如上所述,原因是 React.StrictMode。如果我们在应用中检查我们之前使用 CRA 运行的文件,我们会发现,我们的 <App /> 组件被它包裹:

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

显然,重新渲染并不是一个 bug,或者和库的渲染机制有关的东西。正相反,它是 React 提供的一种调试机制 🤗。

# 什么是 React.StrictMode?

React.StrictMode 是在 2018 年的 16.3.0 版本中引入的组件。一开始,它只用在类组件中,而在 16.8.0 中,它对 hook 同样适用。

就像在版本说明中提及的一样:

React.StrictMode 是帮助应用适应异步渲染的组件

所以它应该用来帮助工程师避免常见的错误,并使他们的 React 应用抛弃过时的 API,从而逐步升级。

这些提示对于更好地调试是有帮助的,因为这个库正在向异步渲染时代迈进,所以大的改动时时发生。

很有用,对吧?

为什么会渲染两次呢?

我们从使用 React.StrictMode 中获得的好处之一是,它帮助我们检测到渲染期生命周期的预期之外的副作用。

这些生命周期有:

  • constructor
  • componentWillMount (或者 UNSAFE_componentWillMount)
  • componentWillReceiveProps (或者 UNSAFE_componentWillReceiveProps)
  • componentWillUpdate (或者 UNSAFE_componentWillUpdate)
  • getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • setState 更新函数 (第一个参数)

所有这些方法都被调用不止一次,所以避免副作用是十分重要的。如果我们无视这个原则,就有可能造成状态不一致问题或者内存泄漏。

React.StrictMode 不能马上检测到副作用,但是它可以通过故意调用一些关键函数两次,来帮助我们发现副作用。

这些函数有:

  • 类组件 constructorrender 以及 shouldComponentUpdate 方法
  • 类组件静态 getDerivedStateFromProps 方法
  • 方法组件的方法体
  • 状态更新函数 (setState 的第一个参数)
  • 传给 useStateuseMemo、或 useReducer 的函数

这个行为肯定对性能有一些影响,但我们不应该担心,因为它只在开发而不是生产环境中发生。

这就是我们只有在开发环境下使用带 React.useState 的组件函数,才可以成功复现渲染两次的原因。Cheers!!

如果你需要继续深入研究 React.StrictMode,你可以阅读 官方文档

如果发现译文存在错误或其他需要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 本文永久链接 即为本文在 GitHub 上的 MarkDown 链接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

阿里巴巴本地生活招聘

团队需要多名前端开发工程师,校招/社招均可,base杭州或上海。 感兴趣的可以发送简历到 tanglie.tl@koubei.com 期待你的加入。