React类组件性能优化
类组件继承React.Component类会有一个shouldComponentUpdate的生命周期,在这个生命周期中可以决定组件是否re-render,默认值为true,true为更新,false为不更新,shouldComponentUpdate会接收两个参数nextProps,nextState,通过判断两个参数是否有更改决定组件是否更新
- nextProps: 表示下一个props。
- nextState: 表示下一个state的值。
import React, { Component } from "react"
export class App extends Component {
// 简单举个例子,实际中肯定要更加深层比较
shouldComponentUpdate(nextProps, nextState){
if(nextProps === this.props) return false
if(nextState === this.state) return false
return true
}
render() {
return (
<div>App</div>
)
}
}
export default App
通过比较props和state的变化去觉得是否需要更新组件,但是每一个页面都需要去判断,那么对于我们开发来说会非常的繁琐,所以React给我们封装了一个React.PureComponent的类,我们的类组件通过继承React.PureComponent则不需要我们手动判断组件是否更新,内部会帮助我们判断数据是否变化
PureComponent的使用方法
当我们继承了PureComponent,我们就不需要担心执行没有意义得到render,下面通过一个简单的例子:页面中有一个text属性,点击按钮把text从'Hello World!'改为'Hello World!',观察render函数是否有执行
import React, { PureComponent } from "react"
export class App extends PureComponent {
constructor() {
super()
this.state = {
text: "Hello World!",
}
}
change() {
this.setState({
text: "Hello World!",
})
}
render() {
console.log("render")
const { text } = this.state
return (
<>
<div>{text}</div>
<button onClick={() => this.change()}>change</button>
</>
)
}
}
export default App
PureComponent源码解析
在ReactBaseClasses.js文件中,给PureComponent组件添加了一个标识
const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
// Avoid an extra prototype jump for these methods.
Object.assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true;
搜索isPureReactComponent属性可查看其作用是用于比较props和state
// 如果是PureComponent组件则判断数据是否发生变化
else if (type.prototype && type.prototype.isPureReactComponent) {
shouldUpdate = !shallowEqual(oldProps, props) || !shallowEqual(oldState, state);
}
shallowEqual是对比两个属性是否一样,注意:这里做的是浅层比较,深层属性发生变化时是不会更新的
import is from './objectIs';
const hasOwnProperty = Object.prototype.hasOwnProperty;
/**
* Performs equality by iterating through keys on an object and returning false
* when any key has values which are not strictly equal between the arguments.
* Returns true when the values of all keys are strictly equal.
*
* 通过遍历对象上的键来执行相等性,
* 当任意键的值在参数之间不严格相等时返回false,
* 当所有键的值严格相等时,返回true。
*/
function shallowEqual(objA: mixed, objB: mixed): boolean {
// is 方法同等 Object.js 这里为了兼容性而封装的 is 方法
// 当两个值相等则返回 true 引用类型则比较地址
if (is(objA, objB)) {
return true;
}
// objA 或者 objB 不为对象时直接返回 false
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false;
}
// 拿到 objA 和 objB 的所有 key
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
// 两个对象的 key 数量不一致则返回 false
if (keysA.length !== keysB.length) {
return false;
}
// 当 objA 和 objB 都有共同的 key 值则比较值是否相同
// Test for A's keys different from B.
for (let i = 0; i < keysA.length; i++) {
if (
!hasOwnProperty.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])
) {
return false;
}
}
return true;
}
shallowEqual函数返回false则更新,返回true则不更新,因为外部调用时进行了取反操作
继承PureComponent需注意
我们通过查看shallowEqual源码发现,当前后的对象引用地址相同时,组件是不会更新的,这个时候就可能会出现不可预计的错误了,React官方是推出了一个名词不可变数据,意思就是不要直接修改state中的数据,下面举个例子
import React, { PureComponent } from "react"
export class App extends PureComponent {
constructor() {
super()
this.state = {
arr: [1, 2, 3],
}
}
add() {
this.state.arr.push(4)
this.setState({
arr: this.state.arr,
})
}
render() {
return (
<>
<ul>
{this.state.arr.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
<button onClick={() => this.add()}>+</button>
</>
)
}
}
export default App
当我们点击添加时,视图是不会发生变化的,add函数通过push修改了原数组,调用setState把this.state.arr传个arr,但是他们的引用地址是相同的,所以React内部以为你没有改变,所以不执行render渲染视图,这样修改属性值也是不建议的
add() {
const newArr = [...this.state.arr]
newArr.push(4)
this.setState({
arr: newArr
})
}
通过一个浅拷贝创建一个新的对象再进行操作,这样就可以触发视图更新了