React总结2:useMemo 与 useCallback 得使用,组件优化方案
前面
先了解一下在react中的class类组件中的性能优化方面,主要集中于一下两点
1.调用setState时,就会触发组件重新渲染,无论前后state是否改变
2.父组件更新,子组件也会自动更新
解决方案:
1.使用 immutable 进行比较,在不相等的时候调用 setState
2.在shouldComponentUpdate 中判断前后的数据(prop,state)是否一致,如果没有变化就返回 false 来阻止更新。
3.使用 pureComponent 代替 component,pureComponent 自带通过 props 和 state 的浅对比来实现 shouldComponentUpdate()
pureComponent 缺点:可能会因深层的数据不一致而产生错误的否定判断,
从而 shouldComponentUpdate 的结果返回 false,界面得不到更新
下面说下 hooks 中的函数式组件如何优化组件性能方案:
在函数式组件中失去了 shouldComponentUpdate ,我发通过判断前后状态来决定是否更新。
在函数式组件中,react 不再区分 mount 和 update 两个状态,也就是说函数组件的每一次调用都会执行其内部的所有略记,会带来较大的性能损耗。在此,hooks 中出现了两个钩子 useMemo 和 useCallback 来解决函数式组件的性能方案。
先看下 useMemo 和 useCallback 的源码
function useCallback<T extends (...args: any[]) => any>(callback: T, deps: DependencyList): T; function useMemo<T>(factory: () => T, deps: DependencyList | undefined): T;
参数形式和 useEffect 一致,useEffect 作用是用于处理副作用,这两个则不行。
useMemo 和 useCallback 都会在组件第一次渲染的时候执行,之后会在依赖改变时再次执行;
都返回缓存的值,useMemo 返回缓存的变量,useCallback 返回缓存的函数。
先说下 useMemo
优点:useMemo包裹的变量,相当于对变量做了缓存,当父组件重新渲染时,变量不会改变==》子组件不会重新渲染
function Test() { const [count, setCount] = useState(1); const [val, setVal] = useState(''); // sum1 每次在 count 与 val 有一个改变的时候都会刷新组件重新执行 sum1。 // 但是这里的计算 sum1 只依赖于 count 的值,在 val 修改的时候没有必要进行 sum1 的计算。 // 这种情况下,我们就可以使用 useMemo,只在count 的值修改时执行计算(sum2) const sum1 = () => { console.log('sum1111'); let sum = 0; for (let i = 0; i < count; i++) { sum += i; } return sum; }; // 每次在 count 的值改变的时候才重新执行计算 const sum2 = useMemo(() => { console.log('sum2222'); let sum = 0; for (let i = 0; i < count; i++) { sum += i; } return sum; }, [count]); return ( <div> <h1>{count}+++{val}+++{sum1()}+++{sum2()}</h1> <div> <button onClick={() => setCount(count + 1)}>+++</button> <input type='text' value={val} onChange={event => setVal(event.target.value)}/> </div> </div> ); }
下面说下 useCallback
为什么使用 useCallback,以及 useCallback 所能带来的性能提升
和 useMemo 类似,当回调函数床底给经过优化的并使用引用相等性去避免非必要渲染的子组件时,他非常有用,和 pureComponent 的作用相同。就是说父组件传递一个函数给子组件的时候,由于父组件的更新会导致该函数重新生成从而传递给子组件的函数引用发生了变化,这就会导致子组件也会更新, 很多情况下子组件的更新并不是必要的,所以通过 useCallback 来缓存传递给子组件的函数。
优点:useCallback包裹的函数,相当于对函数做了缓存,当父组件重新渲染时,函数不会重新定义==》子组件不会重新渲染
代码示例
const Child = ({getNum}) => { return <h1>数据:{getNum}</h1>; }; function Test() { const [count, setCount] = useState(1); const [val, setVal] = useState(''); const sum = useCallback(() => { return Array.from({length: count}, (v, i) => i).reduce((a, b) => a + b); }, [count]); return ( <div> <Child getNum={sum}/> <h1>{count}+++{val}++++{sum()}</h1> <div> <button onClick={() => setCount(count + 1)}>+++</button> <input type='text' value={val} onChange={event => setVal(event.target.value)}/> </div> </div> ); } // 以上只有当 count 改变的时候child组件才会重新渲染
总结:
memo用于包裹子组件;useCallback和useMemo用于父组件向子组件传值时,即如果是组件内部自己用的函数和变量,不需要使用useCallback和useMemo。