PureComponent 精髓

使用 PureComponent 是优化 React 性能的一种常用手段, 相较于 Component, PureComponent 会在 render 之前自动执行一次 shouldComponentUpdate() 函数, 根据返回的 bool 值判断是否进行 render。其中有个重点是 PureComponent 在 shouldComponentUpdate() 的时候会进行 shallowEqual(浅比较)。

PureComponent 的浅比较策略如下:

对 prevState/nextState 以及 prevProps/nextProps 这两组数据进行浅比较:

1.对象第一层数据未发生改变, render 方法不会触发; 2.对象第一层数据发生改变(包括第一层数据引用的改变), render 方法会触发;

PureComponent 的实现

照着上述思路我们来实现 PureComponent 的逻辑

function PureComponent(props) {
this.props = props || {}
this.state = {}
isShouldComponentUpdate.call(this) // 为每个 PureComponent 绑定 shouldComponentUpdate 方法
}
PureComponent.prototype.setState = function(updater, cb) {
isShouldComponentUpdate.call(this) // 调用 setState 时, 让 this 指向子类的实例, 目的取到子类的 this.state
asyncRender(updater, this, cb)
}
function isShouldComponentUpdate() {
const cpState = this.state
const cpProps = this.props
this.shouldComponentUpdate = function (nextProps, nextState) {
if (!shallowEqual(cpState, nextState) || !shallowEqual(cpProps, nextProps)) {
return true // 只要 state 或 props 浅比较不等的话, 就进行渲染
} else {
return false // 浅比较相等的话, 不渲染
}
}
}
// 浅比较逻辑
const shallowEqual = function(oldState, nextState) {
const oldKeys = Object.keys(oldState)
const newKeys = Object.keys(nextState)
if (oldKeys.length !== newKeys.length) {
return false
}
let flag = true
for (let i = 0; i < oldKeys.length; i++) {
if (!nextState.hasOwnProperty(oldKeys[i])) {
flag = false
break
}
if (nextState[oldKeys[i]] !== oldState[oldKeys[i]]) {
flag = false
break
}
}
return flag
}

测试用例

测试用例用 在 React 上提的一个 issue 中的案例, 我们期望点击增加按钮后, 页面上显示的值能够加 1。

class B extends PureComponent {
constructor(props) {
super(props)
this.state = {
count: 0
}
this.click = this.click.bind(this)
}
click() {
this.setState({
count: ++this.state.count,
})
}
render() {
return (
<div>
<button onClick={this.click}>增加</button>
<div>{this.state.count}</div>
</div>
)
}
}

然而, 我们点击上述代码, 页面上显示的 0 分毫不动!!!

揭秘如下:

click() {
const t = ++this.state.count
console.log(t === this.state.count) // true
this.setState({
count: t,
})
}

当点击增加按钮, 控制台显示 t === this.state.count 为 true, 也就说明了 setState 前后的状态是统一的, 所以 shallowEqual(浅比较) 返回的是 true, 致使 shouldComponentUpdate 返回了 false, 页面因此没有渲染。

类似的, 如下写法也是达不到目标的, 留给读者思考了。

click() {
this.setState({
count: this.state.count++,
})
}

那么如何达到我们期望的目标呢。揭秘如下:

click() {
this.setState({
count: this.state.count + 1
})
}

感悟: 小小的一行代码里蕴藏着无数的 bug。

相关链接