什么时候用 useMemo、useCallback?分享自己的看法与 React 新文档的说法
前言
自 react hooks
推出以来,useMemo
、useCallack
几乎就被所有人诟病,对新手更是不友好。什么时候应该用,什么时候不用?用了的话,依赖性一多,可读性就会很差,甚至可能出现 bug。每个人都有自己的理解,看过挺多文章,无论怎么用,多用还是少用,都有不同的看法。
个人观点
这类文章看了很多,国内外的都有,再加上我自己的理解和日常使用情况。
useMemo
一般两种情况下我会用。
- 计算量比较大(怎么样才算下面会讲到,这里不举例子)
- 计算量可能不大,但需要分成很多步才能得到一个结果值。(举个例子)
// 不用 useMemo,可读性不高,代码都在同一级,可能不容易看出只需要用到 data
function App() {
const a = ...;
const b = ...;
const c = ...;
const data = ...用上面这些变量来得到;
return <div>{data.map(...)}</div>
}
// 用 useMemo,可读性增加,代码结构化
function App() {
const data = useMemo(() => {
const a = ...;
const b = ...;
const c = ...;
const result = ...用上面这些变量来得到;
return result;
}, [...])
return <div>{data.map(...)}</div>
}
useCallback
遇到性能问题的时候再用,并且只有当函数有传给子组件的时候,并且子组件有用 React.memo
包裹才会有效果。如果你没有传给子组件,或根本没有子组件,不要滥用!依赖项一多,可读性很差。
比如你用 antd 的 Button 传入一个 onClick
,并用 useCallback
包裹,是没用的,因为 antd 的 Button 没有包裹 React.memo
。除非你用 useMemo
包裹这个 Button
,不过没有这个必要。
现代浏览器和设备,99% 的情况下都不会有性能问题,除非一些极少的特定情况,如果有性能问题,开发的时候就会遇到了,这时候再来做这个优化也不迟。
React 新文档
链接,这里只挑选一部分重点进行翻译,详细的还是看原文比较好。
useMemo
怎么判断计算昂不昂贵
一般而言,除非您要创建或遍历数千个对象,否则它可能并不昂贵。你可以添加一个控制台日志来衡量一段代码所花费的时间:
console.time('filter array');
const visibleTodos = filterTodos(todos, tab);
console.timeEnd('filter array');
console.time('filter array');
const visibleTodos = useMemo(() => {
return filterTodos(todos, tab); // Skipped if todos and tab haven't changed
}, [todos, tab]);
console.timeEnd('filter array');
useMemo
不会使第一次渲染更快。它只会帮助您跳过不必要的更新工作。
应该在所有地方添加 useMemo 吗?
优化useMemo
只在少数情况下有价值:
- 你要放在 useMemo 中的计算明显很慢,并且它的依赖关系很少发生变化。
- 你将其作为 prop 传递给 memo 包装的组件。如果值没有发生变化,你想跳过重新渲染。记忆化可以让你的组件仅在依赖项不同的情况下重新渲染。
- 你传递的值后来被用作某个 Hook 的依赖项。例如,另一个 useMemo 计算值可能依赖于它。或者你可能从 useEffect 依赖于这个值。
在实践中,您可以通过遵循一些原则来避免大量记忆:
- 当一个组件包装其他组件时,让它接受 JSX 作为 children。这样,当包装组件更新自己的状态时,React 知道其子组件不需要重新渲染。
- 优先使用本地状态,不要将状态向上提升得比必要的更高。例如,不要在树的顶部或全局状态库中保留短暂状态,例如表单和是否悬停的项。
- 保持你的渲染逻辑纯粹。如果重新渲染组件会导致问题或产生一些明显的视觉瑕疵,则这是你组件中的一个 bug!修复 bug 而不是添加记忆化。
- 避免不必要的更新状态的 Effects。React 应用程序中大多数性能问题都是由 Effects 导致的更新链引起的,这会导致组件一遍又一遍地重新渲染。
- 尝试从 Effects 中删除不必要的依赖项。例如,在 Effect 内部或组件外部移动某些对象或函数通常比记忆更简单。
useCallback
与 useMemo 有什么关系?
useMemo
缓存调用函数的结果useCallback
缓存函数本身
你应该在所有地方添加 useCallback 吗?
缓存函数useCallback
仅在少数情况下有价值:
- 您将它作为 prop 传递给包装了
memo
的组件。你想跳过重新渲染在这个值没有发生变化的时候。记忆让你的组件只有在依赖关系发生变化时才重新渲染。 - 您传递的函数稍后用作某些 Hook 的依赖项。例如,另一个包裹了
useCallback
的函数依赖于它,或者你的useEffect
依赖于这个函数。
useCallback
不会阻止创建函数。render 时总是会创建一个函数,但 React 会忽略它并在没有任何改变的情况下返回一个缓存的函数。
在实践中,您可以通过遵循一些原则来避免大量记忆:
与 useMemo
相同
个人感想
必须使用的情况
- 计算量特别大的时候,像文档所说,创建或遍历数千个对象,要使用
useMemo
。其它情况用不用看个人习惯。 - 子组件重新渲染造成的影响很大的时候,子组件包裹
memo
,并且传递给子组件的函数用useCallback
包裹。否则使用useCallback
几乎没有作用。 - 你开发了一个类似
react-use
、ahooks
的 npm 包,返回的函数方法要包裹useCallback
,使用的人可能需要做优化。我就遇到过这种情况,准备给库提 pr,然后发现最新版本加了。
把新文档看了下来,觉得我的个人观点和使用,是没有什么问题的。看过挺多 ui 库的源码,也贡献过不少次代码,其实也都是差不多的,没有无脑用。从 react-labs 3月报告 来看 react-forgot
已经在 facebook
内部试用了,希望能早点稳定然后开源吧,这样我们就不用再纠结这些了。
转载自:https://juejin.cn/post/7215824615648804921