当组件有 children 时 React.PureComponent 不起作用?

React.PureComponent doesn't work when the Component has children?

在 React 中使用 PureComponent 来提高渲染性能似乎是一种常见的技术。但是,使用具有 children 作为道具的 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 秒保持记录 're-render'。似乎 children(<div />) 是上面 App 组件的唯一 prop 并且永远不会改变,为什么 App 仍然得到 re-rendered?

注意:如果有任何混淆,问题与为什么上面的 PureComponent 的 SCU(shouldComponentUpdate) 挂钩 return true 相同,因为似乎没有任何道具改变?

这里发生的事情是你实际上在调用 ReactDOM.render(),Page(或 App,我想你这里有错字)组件将触发它的 render() 函数,而不管使用 ComponentPureComponent.

PureComponent 可以帮助减少不必要的渲染的方法是当 有一个 prop 变化时,PureComponent 将在 this.propsnextProps 上进行浅比较确定此 PureComponent 是否需要调用 render().


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

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. 两个children都收到一个固定道具("fixed value"字符串)
  2. 每 1 秒,parent <App /> 改变 value 状态,因此它 re-renders,导致其所有 children 到 re-render还有。
  3. 但是 因为 <PureChild /> 是一个 PureComponent,它对它的旧 props 和传入的新 props 进行了浅层比较,并注意到两个 props 都是 "fixed value" ,因此它不会触发渲染!

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

现在根据 ReactDOM

的文档

ReactDOM.render() controls the contents of the container node you pass in. Any existing DOM elements inside are replaced when first called. Later calls use React’s DOM diffing algorithm for efficient updates.

ReactDOM.render() does not modify the container node (only modifies the children of the container). It may be possible to insert a component to an existing DOM node without overwriting the existing children.

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

现在来到 PureComponent。文档指出

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

时才扩展 PureComponent

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

勾选这个CodeSandbox

 console.log(<div /> === <div />) // false

<App /> 的每次重新渲染中,一个 new React Element was created by React.createElement(div, null), thus this.props.children will be different from nextProps.children though they look the same in JSX.

事实上,真正的问题是 props.children 的引用(否则值如果是原始类型)每次父重新渲染时都会改变,并且 React.PureComponent 通过引用比较 props 包含不变性。

根据 React.PureComponent

的文档

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

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

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