当组件有 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()
函数,而不管使用 Component
或 PureComponent
.
PureComponent 可以帮助减少不必要的渲染的方法是当 有一个 prop 变化时,PureComponent 将在 this.props
和 nextProps
上进行浅比较确定此 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>
}
}
注意这几点:
- 两个children都收到一个固定道具("fixed value"字符串)
- 每 1 秒,parent
<App />
改变 value
状态,因此它 re-renders,导致其所有 children 到 re-render还有。
- 但是 因为
<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.children
与 nextProps.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
在 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()
函数,而不管使用 Component
或 PureComponent
.
PureComponent 可以帮助减少不必要的渲染的方法是当 有一个 prop 变化时,PureComponent 将在 this.props
和 nextProps
上进行浅比较确定此 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>
}
}
注意这几点:
- 两个children都收到一个固定道具("fixed value"字符串)
- 每 1 秒,parent
<App />
改变value
状态,因此它 re-renders,导致其所有 children 到 re-render还有。 - 但是 因为
<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 可能 return 漏报更深层次的差异。因此,当您尝试比较 this.props.children
与 nextProps.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