tips
:欢迎关注我在github的博客点击查看 。
使用React.pureComponent,React.memo做性能优化
讲这两个api前需要提及React组件的更新,看这图:
如果shouldComponentUpdate 返回false,那就一定不用rerender(重新渲染 )更新这个组件了,不用执行调用render函数,返回React elements去做比对(vdoms diff),而且这个组件的子组件也不用去调用shouldComponentUpdate判断是否要更新了,因为父组件没更新(自己也没改变state)。但是如果shouldComponentUpdate 返回true,会进行组件的React elements比对,如果相同,则不用真实rerender这个组件,否则会rerender。
React.pureComponent
React.PureComponent,字面意思就是纯组件,可类比存函数概念,所以当有一致的渲染输入(props和state)时,因为有同样的渲染效果,所以就不rerender了,以此优化性能。
React.Component是react的普通类组件,有个生命周期:在重新渲染之前应调用shouldComponentUpdate。在初始渲染之后,当接收到新的props或state时,在重新渲染之前应调用shouldComponentUpdate()。默认为true。
而React.PureComponent与React.Component之间的区别在于,React.Component虽然可以在shouldComponentUpdate()这个生命周期中进行自定义比较决定是否rerender,这样可以做性能优化。但是React.PureComponent的shouldComponentUpdate默认内置去浅比较prop和state来决定要不要rerender组件,解决了大量在Component虽然可以在shouldComponentUpdate写差不多模板代码的问题。
React.memo
React.pureComponent 服务于class组件,React.memo服务于函数组件,两者服务对象不同,功能相同。有点不同的是,React.memo只会浅比较props,以前函数式组件是stateless的,所以浅比较props也就够了,现依旧如此,useState hook带来的state改变和useContext hook带来的context改变,React.memo包(这货是个高阶组件)的组件仍会rerender。而且React.memo可以传第二个参数来实现类似shouldComponentUpdate的功能自定义决定组件是否rerender => React.memo(MyComponent, areEqual);
。当然,return 值的意义两者反着来。
结论
也就是React.PureComponent与React.memo默认帮你用浅比较去决定要不要rerender,做性能优化。
当然,这边要注意不要mutable地去改动两者的props或者PureComponent的state,这样浅比较的结果为false,也就是这样你就算改了也不会rerender, 除非调用forceUpdate()强制更新。
可是,这样很多同学就会有事没事就React.PureComponent,React.memo一把梭,好像吃不用钱的蜜糖。但是,甲之蜜糖 乙之砒霜,并不是所有场合都适合使用这两个东东,用pureComponent会进行浅比较state和props,这个比较也是需要开销的,所以当你知道组件一定需要重新渲染时,就不要用pureComponent或者shouldComponentUpdate去做浅比较了,shouldComponentUpdate默认值为true,省了这个浅比较,直接去比较React元素有没有变,没有变就不更新,有变才更新。
以下内容来自React开发团队
If we recommended that PureComponent be used everywhere, it would probably be the default already. Rather -- the comparison to decide whether or not a component should be rerendered costs something, and in the case that you do want to rerender, all of the time spent checking whether you should have rerendered is wasted.
旦总还专门发推 (不是朱一旦,手动狗头)
使用react hook: useCallback, useMemo做性能优化
这两个因为作用有点类似,都是用记忆计算结果来避免render的时候重新做一些计算,节省开销。不同的是,useMemo是记忆值,也就是返回值:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
而useCallback是记忆函数,也就是返回一个函数,
const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b],);
useCallback(fn, deps) 等同于 useMemo(() => fn, deps).
而且我原以为会缓存之前多次计算过的东西,没想到测试了下,只会缓存记忆上一次的,所以只有上一次和本次的依赖值相同时,才会读取上一次的计算结果,但是上一次之前的就算有跟本次的依赖值相同的,依旧要重新计算。如下图
useCallback 可以确保在重新渲染之间那个回调不会发生变化,除非依赖(第二个参数)改变
举个例子
图中RenderCallLogCallControl组件里面有一个onTransfer方法,如果不写useCallback,那么每次RenderCallLogCallControl组件rerender,也就是这个函数组件会被重新执行一次,因而onTransfer会跟着被重新创建,就算onTransfer还是一样的函数内容,但实际上引用地址不一样,那么EvSmallCallControl和其子组件TransferCallButton接收到onTransfer的onTransfer prop即有变动,该组件因此也会rerender。
这种情况并不想因为onTransfer被重复创建而rerender组件,性能浪费,那么可以用useCallback把这个onTransfer函数memo起来,让其不会变动(除非依赖transferRef改变), prop有onTransfer的组件也不会因此多做没必要的rerender。
reference
Deep dive with the React DevTools profiler
Introducing the React Profiler
Optimizing Performance
React Top-Level API
should-i-use-react-purecomponent-everywhere