React.PureComponent 在组件有孩子时不起作用?

Posted

技术标签:

【中文标题】React.PureComponent 在组件有孩子时不起作用?【英文标题】:React.PureComponent doesn't work when the Component has children? 【发布时间】:2018-06-14 21:50:05 【问题描述】:

使用 PureComponent 来提高 React 中的渲染性能似乎是一种常见的技术。但是,使用以子作为道具的 PureComponent 时似乎并非如此。

class App extends React.PureComponent 
  render() 
    console.log('re-render') 
    return <div>this.props.children</div>
  


const render = () => 
  ReactDOM.render(
    <App>
      <div />
    </App>,
    document.getElementById('app')
  )
  setTimeout(render, 1000)


render()

结果是控制台每 1 秒记录一次“重新渲染”。似乎 children(&lt;div /&gt;) 是上面 App 组件的唯一道具并且永远不会改变,为什么 App 仍然被重新渲染?

注意:如果有任何混淆,问题是一样的,为什么上面 PureComponent 的 SCU(shouldComponentUpdate) 钩子返回 true,因为似乎没有任何道具发生变化?

【问题讨论】:

请问您为什么在render 内调用render?当您执行ReactDOM.render(..) 时,您已经渲染了您的应用程序,然后您再次递归调用它。这不是导致您的应用被多次重新渲染吗? @margaretkru 我故意为它触发重新渲染以测试上面 App 纯组件的 SCU。补充说明:) 【参考方案1】:

这里发生的事情是你实际上是在调用ReactDOM.render(),页面(或应用程序,我想你这里有错字)组件将触发其render() 函数,无论使用ComponentPureComponent

PureComponent 可以帮助减少不必要的渲染的方式是当prop 发生变化时,PureComponent 会对this.propsnextProps 做一个浅比较,以确定这个PureComponent 是否需要调用@ 987654327@.


我刚刚为你做了这个例子:

class App extends React.PureComponent 
  state = value: 0

  componentDidMount() 
    setInterval(() => 
      this.setState(value: Math.random())
    , 1000)
  

  render() 
    return (
      <div>
        <PureChild value="fixed value"/>
        <ImpureChild value="fixed value"/>
      </div>
    )
  


class PureChild extends React.PureComponent 
  render() 
    console.log('rendering PureChild')
    return <div>this.props.value</div>
  


class ImpureChild extends React.Component 
  render() 
    console.log('rendering ImpureChild')
    return <div>this.props.value</div>
  

注意这几点:

    两个孩子都收到一个固定的道具(“固定值”字符串) 每 1 秒,父级 &lt;App /&gt; 更改 value 状态,因此它重新渲染,导致其所有子级也重新渲染。 但是由于&lt;PureChild /&gt;是一个PureComponent,它对它的旧props和传入的新props做了一个浅层的比较,并注意到两个props都是"fixed value",因此它不会触发渲染!李>

如果您运行此代码并打开控制台,您将每 1 秒看到一次“rendering ImpureChild”,但“rendering PureChild”只会出现一次。

【讨论】:

添加了注释,造成混乱是我的坏事,这可能不是我的问题,但感谢您的努力!【参考方案2】:
 console.log(<div /> === <div />) // false

&lt;App /&gt; 的每次重新渲染中,React.createElement(div, null) 创建了一个 React Element,因此this.props.children 将不同于nextProps.children,尽管它们在JSX 中看起来相同.

实际上,真正的问题是props.children 的引用(如果是原始类型,则为其他值)每次父重新渲染时都会发生变化,并且 React.PureComponent 会通过引用来比较 props,以包含不可变性。

【讨论】:

【参考方案3】:

现在根据ReactDOM的文档

ReactDOM.render()控制你传递的容器节点的内容 in. 任何现有的 DOM 元素在第一次调用时都会被替换。 之后的调用使用 React 的 DOM diffing 算法进行高效更新。

ReactDOM.render()不修改容器节点(只修改 容器的孩子)。可以插入一个 组件到现有的 DOM 节点而不覆盖现有的 孩子们。

ReactDOM 从第二次开始,只需使用它在其他地方使用的差异算法更新 React 组件,因此它不是 ReactDOM,这会导致重新渲染。您可以通过在 App 组件中添加 componentWillMount 方法来验证这一点,并检查它是否只被调用一次

现在来到 PureComponent。文档指出

React.PureComponent’s shouldComponentUpdate() 只是浅比较对象。如果这些包含复杂的数据结构,则可能会为更深层次的差异产生假阴性。仅当您希望拥有简单的 props 和 state 时才扩展 PureComponent

所以这里有一个问题,PureComponent 可能会返回假阴性以获得更深层次的差异。因此,当您尝试比较 this.props.childrennextProps.children 是否相等时,您会发现它返回 false 并因此再次触发重新渲染

检查这个CodeSandbox

【讨论】:

谢谢先详细解答!补充说明,我对纯组件部分感兴趣,您能不能详细说明一下 props.children 的最后一部分,为什么 this.props 和 nextProps 会发生变化?【参考方案4】:

根据 React.PureComponent 的文档

1)。 PureComponent 实现 shouldComponentUpdate() 与浅 props 和状态比较,将检查页面是否需要重新渲染

2)。如果 props 或 state 中有复杂的对象,那么 PureComponent 会给出误报,必须运行强制更新

3)。父组件的变化不会更新子组件,所以 PureComponent 的子组件也应该是 PureComponent

【讨论】:

以上是关于React.PureComponent 在组件有孩子时不起作用?的主要内容,如果未能解决你的问题,请参考以下文章

重构 pure() 与 React.PureComponent

react---pureComponent的理解

React.Component 与 React.PureComponent(React之性能优化)

React 顶层 API

解读 React.memo

创建一个返回反应组件类的打字稿函数