之所以将useCallback和useMemo放到一起,从某种意义上说,他们都是性能优化的始作俑者,他们也有很多的共性。我们先来回顾一下class组件性能优化的点:
setState
,就会触发组件的重新渲染,无论前后 state
是否相同介于这俩点,我们一般使用 immutable
进行比较,在不相等的时候调用 setState
, 在 shouldComponentUpdate
中判断前后的 props
和 state
,如果没有变化,则返回 false
来阻止更新。
但是当hooks到来后,没有了shouldComponentUpdate函数。无法判断前后的props是否相同,从而是不是要从新渲染,useEffect函数并没有区分是mount还是update,这样一来,也就意味着,每次组件的更新都会执行其复杂的逻辑,性能的消耗将是非常大的。
所以,在hooks时代,就诞生了useCallback和useMemo这样的性能优化函数,其实,useCallback和useMemo和useEffect的参数一致,但是,useEffect可以处理副作用,而后俩者不可以。useCallback
和 useMemo
都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行;并且这两个hooks都返回缓存的值,useMemo
返回缓存的 变量,useCallback
返回缓存的 函数。
还记得在class组件时代我们如何做优化吗?对,没错,俩中方法:
(1)pureComponent,对props进行浅比对
(2)shouldComponentUpdate根据返回值来处理是否要更新
在function时代,已经没有了pureComponent和shouldComponentUpdate这种东西,而是react提供了React.memo这样的高阶组件,与pureComponent和相似,但是,这个高阶组件并不是适用于class组件,而只为function组件服务。相比于 PureComponent ,React.memo()
可以支持指定一个参数
,可以相当于 shouldComponentUpdate
的作用,因此 React.memo() 相对于 PureComponent 来说,用法更加方便。
用法:
function MyComponent () {}
function areEqual(prevProps, nextProps) {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
}
export default React.memo(MyComponent, areEqual);
看上面的代码,其实,用法很简单。在 Function Component 之外,在声明一个 areEqual
方法来判断两次 props
有什么不同,如果第二个参数不传递,则默认只会进行 props 的浅比较
。最终 export 的组件,就是 React.memo() 包装之后的组件。在某些情况下,React.memo的第二个参数是必须的。
总的来说,React.memo()方法,是让整个组件要不要re-render,记住这一点很重要,但是,有的时候,我们想要组件的某一部分要不要re-render,而不是整个组件要不要re-render.怎么办?yes,就是useMemo();
用法:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useMemo() 返回的是一个 memoized 值,只有当依赖项(比如上面的 a,b 发生变化的时候,才会重新计算这个 memoized 值)
memoized 值不变的情况下,不会重新触发渲染逻辑。说到渲染逻辑,需要记住的是 useMemo() 是在 render 期间执行的,所以不能进行一些额外的副操作,比如网络请求等。如果没有提供依赖数组(上面的 [a,b])则每次都会重新计算 memoized 值,也就会 re-redner。
const fnA = useCallback(fnB, [a])
useCallback的用法其实和useMemo是一样的,但是他们唯一的区别是,useCallback是缓存了函数。
场景:在开发中,你会遇到这样的场景,需要从父组件中传递一个函数到子组件,如果我们不用useCallback包裹,那么也就意味着,只要父组件有更新,都会向子组件去传递一个新函数,虽然说每次传递的函数都一样,但是依旧是俩个不同的对象。但是如果使用了useCallback, useCallback 就会根据依赖项是否发生变化,从而决定是否返回一个新的函数,函数内部作用域也随之更新。
总的来说:
在子组件不需要父组件的值和函数的情况下,只需要使用 memo
函数包裹子组件即可。
如果有函数传递给子组件,使用 useCallback
如果有值传递给子组件,使用 useMemo
useEffect
、useMemo
、useCallback
都是自带闭包的。也就是说,每一次组件的渲染,其都会捕获当前组件函数上下文中的状态(state
, props
),所以每一次这三种hooks的执行,反映的也都是当前的状态,你无法使用它们来捕获上一次的状态。对于这种情况,我们应该使用 ref
来访问。