React-memo (useMemo, useCallback)
在react中,当我们setState之后,若值发生变化,则会重新render当前组件以及其子组件 (默认情况下),在必要的时候,我可使用memo (class组件则对应shouldComponentUpdate、PureComponent)进行优化,来减少无效渲染。memo是一个高阶组件,接受一个组件作为参数,并返回一个原组件为基础的新组件,而在memo内部,则会使用Object.is来遍历对比新旧props是否发生变化,以决定是否需要重新render。
在我们使用memo包裹子组件的时候,往往需要父组件配合使用useMemo,useCallback等,来缓存传给子组件props中的引用类型的值和方法 (function其实也是引用类型数据的一种,useCallback也是useMemo的一种特殊情况),因为对于react函数组件,其本身就是个render函数,每次re-render之后,都会重新执行此函数,而每次执行的时候就会产生一个新的函数作用域,因此默认每次都会创建一个新的变量,如果是引用类型,则会造成Object.is返回false,故而无法达到防止无效渲染的目的。此时我们需要在父组件传给子组件props的值的时候,缓存引用类型数据,来保证在依赖没有变化的情况下,始终返回同一个引用地址。
注意:只有在组件渲染发生了性能问题的时候,例如组件内容较为复杂,render过程比较慢,我们才使用memo (shouldComponentUpdate、PureComponent) 进行优化,否则你会得不偿失,恰得其反,反而造成性能的下降。因为本身在遍历props进行对比的过程,就需要一定的执行时间,如果组件较小,re-render的代价比对比props的代价更低,这时候我们就不适合使用memo (shouldComponentUpdate)。
useMemo、useCallback其实就是在更新阶段的时候,对比依赖是否发生变化,若发生变化则创建新的值,若没有,则返回旧的值,useMemo的update阶段源码如下:
function updateMemo<T>(nextCreate: () => T,deps: Array<mixed> | void | null,
): T {const hook = updateWorkInProgressHook();const nextDeps = deps === undefined ? null : deps;const prevState = hook.memoizedState;if (prevState !== null) {// Assume these are defined. If they're not, areHookInputsEqual will warn.if (nextDeps !== null) {const prevDeps: Array<mixed> | null = prevState[1];if (areHookInputsEqual(nextDeps, prevDeps)) {return prevState[0];}}}const nextValue = nextCreate();hook.memoizedState = [nextValue, nextDeps];return nextValue;
}