React 作为一个视图框架,一般情况下速度已经很快了,并且在 React16 新推出的 Fiber 架构中,通过时间切片及高优先级任务中断来尽快相应用户的操作。但是有些我们忽略点,造成了不必要的组件 re-render,从而造成性能的浪费。
如何阻止不必要的 re-render
使用 pureComponent
当组件更新时,pureComponent
会在 shouldComponentUpdate
帮我们比较一下 props
跟 state
, 如果组件的 props
和 state
都没发生改变,render
方法就不会触发。具体就是 React 自动帮我们做了一层浅比较:
if (this._compositeType === CompositeTypes.PureClass) {
shouldUpdate = !shallowEqual(prevProps, nextProps) || !shallowEqual(inst.state, nextState);
}
return shouldUpdate;
而 shallowEqual
又做了什么呢?会去判断 prev
跟 next
的 key 是否一致,且对应的值跟引用是否相等。(注意这里是引用是否相等,两个即使内容相同的对象,也是不相等的)
虽然有了一层浅比较,但是更多的时候,由于我们的写法的问题,使得这层比较失效了。
// Child extends PureComponent
class Parent extends Component {
render() {
return (
<div>
<Child
style={{ width: 100 }}
onClick={() => {
// do something
}}
/>
</div>
);
}
}
上面的这种写法,会导致每次 Child
必定重复更新,因为每次传递进来的 onClick 和 style 都是与上次不相等的。
将这些不变的数据使用同一个引用
// Child extends PureComponent
class Parent extends Component {
onChildClick = () => {
// do something
};
childStyle = { width: 100 };
render() {
return (
<div>
<Child onClick={this.onChildClick} style={this.childStyle} />
</div>
);
}
}
使用 React.memo
React.memo
为高阶组件。它与 React.PureComponent
非常相似,但它适用于函数组件,但不适用于 class 组件。
如果你的函数组件在给定相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo
中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。
默认情况下其只会对复杂对象做浅层对比(与 pure 类似),如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。
// 如果 areEqual 不传递的话使用默认的浅比较
export default React.memo(MyComponent, areEqual);
再说说上面同一个引用的问题,假设现在有下面这样一个组件
const Child = React.memo(MyComponent);
const Parent = () => {
onChildClick = {
// do something
};
return (
<div>
<Child onClick={onChildClick} />
</div>
);
};
这里就会有上面说的问题,每一次re-render 的时候 传递给 Child
的 onClick 都是新生产的 function,所以导致浅比较失效。
这里我们可以用 useCallback
去进行优化。(关于 useCallback 简单使用介绍请查看 React hooks useCallback 指北)
const Child = React.memo(MyComponent);
const Parent = () => {
const onChildClick = useCallback(() => {
// do something
}, []);
return (
<div>
<Child onClick={onChildClick} />
</div>
);
};
使用 react-redux 时候,优化 re-render
可以在传递 props
时就做对比优化,不用再 shouldComponentUpdate
阶段去做
- 使用了
connect
,每次store更新,不管是否与自身有关,都会导致组件re-render。其实react-redux
是支持我们去做优化的。
/*
options
{
context?: Object,
pure?: boolean,
areStatesEqual?: Function,
areOwnPropsEqual?: Function,
areStatePropsEqual?: Function,
areMergedPropsEqual?: Function,
forwardRef?: boolean,
}
*/
function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)
我们可以通过 areStatePropsEqual
函数去做对比优化。
- 如果使用了
hooks
,怎么优化呢
const result : any = useSelector(selector : Function, equalityFn? : Function)
通过 equalityFn
去做优化。
总结
- 类组件使用 PureComponent 去做优化,函数组件使用 React.memo 去做优化
- 将这些不变的数据使用同一个引用,减少匿名函数的使用
- react-redux 提前关注我们store,对相关store做处理