MeloGuo, react
Back

React.PureComponent

在 v15.3 中增加了一个新 API,从名字可以看出 PureComponent 应该比 Component 更纯。文档中解释,它们的不同之处在于 React.Component 没有实现 shouldComponentUpdate(),但是 React.PureComponent实现了它。采用对属性和状态用浅比较的方式进行更新。

如何浅比较?

浅比较中会比较 Object.keys(state || props) 的长度是否一致,每一个 key 是否两者都有,并且是否是一个引用,也就是只比较了第一层的值,确实很浅,所以深层的嵌套数据是对比不出来的。

function shallowEqual (prevVal, newVal) {
const prevValKeys = Object.keys(prevVal)
const newValKeys = Object.keys(newVal)
if (prevValKeys.length !== newValKeys.length) { return false }
return prevValKeys.every(prevValKey => newValKeys.indexOf(prevValKey) > -1 && prevVal[prevValKey] === newVal[prevValKey])
}

PureComponent 是如何使用 shallowEqual 的?

先看一个真正 PureComponent 的例子:

class App extends React.PureComponent {
state = {
items: [1, 2, 3]
}
handleClick = () => {
const { items } = this.state
items.pop()
this.setState({ items })
}
render () {
return (
<div>
<ul>
{this.state.items.map((item, index) => <li key={index}>{item}</li>)}
</ul>
<button onClick={this.handleClick}>删除</button>
</div>
)
}
}

在这个组件中,你会发现点击删除按钮是无效的!原因就是新的state.item和旧的state.item指向同一个引用,所以被 PureComponent 拒绝了渲染。这也正是 PureComponent 的特性。在 Component 中使用 shallowEqual 这个函数模拟一下便也有这样的效果。

class App extends React.Component {
state = {
items: [1, 2, 3]
}
// 模拟 PureComponent
shouldComponentUpdate (nextProps, nextState) {
return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState)
}
handleClick = () => {
const { items } = this.state
items.pop()
this.setState({ items })
}
render () {
return (
<div>
<ul>
{this.state.items.map((item, index) => <li key={index}>{item}</li>)}
</ul>
<button onClick={this.handleClick}>delete</button>
</div>
)
}
}

所以在 PureComponent 中更新引用类型的数据时不要使用同一引用,而应该使用一个新的引用,如下:

handleClick = () => {
const { items } = this.state
items.pop()
// 使用一个新的引用
this.setState({ items: [...items] })
}

这样便不会被判定为 state 未改变。但是对于不变的数据却应该使用同一个引用,不要每次 render 都让引用变化引发不必要的重渲染:

update () {
console.log('update')
}
render () {
// 每次渲染都会传入一个新的引用的函数
return <div onClick={this.update.bind(this)} />
}

解决方法便是每次传入相同引用的函数

update = () => {
console.log('update')
}
render () {
return <div onClick={this.update} />
}

在处理对象时也有相似的问题,例如为属性指定默认值:

render () {
// 在 JS 中 {} 不等于 {}
return <div data={this.state.data || {}} />
}

这种情况每次传入的默认值都会被判定为新值从而进行渲染,所以应该使用一个变量固定引用。

defalutData = {}
render () {
return <div data={this.state.data || this.defalutData} />
}

总结

PureComponent真正起作用的,只是在一些纯展示组件上,复杂组件用了也没关系,但是在 shadowEqual 那里便会被拦截,不过记得 props 和 state 不能使用同一个引用哦。

著作权声明

本文作者 郭梓梁,首次发布于 MeloGuo Blog,转载请保留以上链接


GitHub · guoziliang199606@gmail.com · 微信
CC BY-NC 4.0 © Melo Guo.RSS