定制网站【1024用代码改变世界】useMemo 和 useCallback|React.memo使用场景

定制网站欢迎来到我的博客
📔定制网站博主是一名大学在读本科生,定制网站主要学习方向是前端。
🍭定制网站目前已经更新了【Vue】、【React–定制网站从基础到实战】、【TypeScript】定制网站等等系列专栏
🛠定制网站目前正在学习的是🔥 R e a c t / 小程序 React/小程序 React/小程序🔥,定制网站中间穿插了一些基础知识的回顾
🌈博客主页👉

😇本文目录😇

定制网站本文被专栏收录

🕹坚持创作✏️,一起学习📖,码出未来👨🏻‍💻!

前言

/ useCallback都是React定制网站内置的用于性能优化的hook,定制网站它们常常被开发人员用来包裹(缓存memory),定制网站但是真的是所有的数据、函数、定制网站变量都需要使用useMemo / useCallback去缓存吗?
定制网站可直接看结论。

useMemo / 定制网站都是用以性能优化的hook,定制网站开发者经常担心两次渲定制网站染间的重复计算,定制网站而去过度使用useMemo / useCallback,定制网站担心性能问题的开发者们,定制网站给几乎每个变量都套上了useMemo,定制网站给每个函数都套上了useCallback……其实这是不可取的,这让代码看起来像是必须使用这两个hook去优化一样,无处不在。

本文希望通过分析 useMemo/useCallback 的目的、方式、成本,以及具体使用场景,帮助开发者正确的决定如何适时的使用他们。赶时间的读者可以直接拉到底部看结论。

我们先从 useMemo/useCallback 的目的说起。

何时应该使用useMemo / useCallback ?

防止不必要的 effect

小新在编码的过程中,如果effect有依赖的变量,我就会把effect里的内容提到effect外面,包装成一个函数,再用useCallback去缓存这个函数,那么只要这个变量不变化,effect依赖的这个函数也不会改变(不使用useCallback缓存的话,此函数的内存地址可能会发生变化,哪怕其内部不改变)。

const Component = () => {  const a = React.useMemo(() => ({ test: 1 }), [])  React.useEffect(() => {    // dosomthing  }, [a])  return (    <div>{a.test}</div>  )}const root = ReactDOM.createRoot(document.getElementById('root'));root.render(<Component />);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

只有a的值改变时,dosomthing才会重新触发,而a被useMemo缓存了,这就导致非必要时,effect不会重新创建,这是好的优化;

useCallback也是一样的,(useCallback其实是useMemo的语法糖)

const Component = () => {  const ajax = React.useCallback(() => {    console.log('^ajax somthing^!')  }, [])  React.useEffect(() => {    // dosomthing    ajax()  }, [ajax])  return (    <div></div>  )}const root = ReactDOM.createRoot(document.getElementById('root'));root.render(<Component />);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

此代码段中的Component组件,只有当ajax函数变化时才会重新创建一个effect,这就导致,我们可以把仅需要在页面首次加载时发送的ajax请求封装成一个函数,并且用useCallback优化缓存下来,这是好的优化;

防止不必要的re-render

我们首先思考,当什么情况出现时,组件才会re-render

  • 当本身的props或state改变时;
  • context上下文的value改变时,使用该值的组件都会重新render;
  • 当父组件重新render时,哪怕传入子组件的props没有发生改变,子组件(们)也会随着父组件的render,重新render;

第三个re-render经常被开发者忽视,其实这一点很重要!!

例如,

const Component = () => {  const [state, setState] = React.useState(1);  const onClick = React.useCallback(() => {    console.log('^click somthing^!')  }, []);  return (	// 哪怕onClick使用了useCallback缓存优化,但是自组件仍会re-render    <Child onClick={onClick} />  )}const root = ReactDOM.createRoot(document.getElementById('root'));root.render(<Component />);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

哪怕onClick使用了useCallback缓存优化,但是自组件仍会re-render。这里的useCallback似乎是无效的。

那么,怎么让其生效呢?

我们可以搭配React.memo去使用:

const PageMemoized = React.memo(Page);
  • 1

React.memo本质是一个HOC,它接受一个组件作为参数。被memo包裹的Page组件,会在Page组件的父组件Component重新render时,对比传入Page组件的props( 浅比较,复杂对象只比较第一层),若props没有发生改变,则Pages组件就不会re-render

所以, 必须同时缓存 onClick 和组件本身,才能实现 Page 不触发 re-render。

PageMemoized会在父组件重新render时,浅比较传入的onClick是否变化再决定PageMemoized组件是否需要re-render,但是onClick正好被useCallback缓存了,所以这里的子组件不会re-render(●–●)

但是,如果PageMemoized组件从父组件不止接受了onClick一个prop,那么前面的优化就前功尽弃。比如,

// 省略重复代码<PageMemoized onClick={onClick} value={[1, 2, 3]} />
  • 1
  • 2
  • 3

每次父组件重新re-render时,传入子组件的onClick函数虽然没有改变(useCallback的功劳),但是value并没有做任何缓存,此时,子组件PageMemoized,还是逃脱不了re-render的命运……

怎么解决呢?

// 省略重复代码const value = useMemo(() => {  return [1, 2, 3]}, [])// ...<PageMemoized onClick={onClick} value={[1, 2, 3]} />
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

这样的话,value变量也被缓存起来了,父组件re-render时,自组件并没有re-render。

由此我们知道, 必须同时缓存 所有的prop 和组件本身,才能实现子组件 不触发 re-render。

如何判断子组件是否需要缓存?

如果所有的子组件都需要缓存,那未免也太麻烦了……不光需要memo子组件,还需要将现有的props都进行缓存,并且还包括了后续编码可能出现的其他props……

除此之外,还有更严重的后果,如果项目中的组件缓存过多的话,可能会导致 项目在首次初始化时因为组件缓存被拖慢渲染时间。

所以,局部的,有选择的去使用memo,比全局都使用memo更加恰当、更加优雅。

至于怎样判断组件的渲染成本,可以借助React Devtool等工具去判断,或者根据开发者经验人工判断。

防止不必要的计算

React官方文档介绍:

useMemo返回一个 memoized 值。
把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。

什么才是高开销呢?

借助前端经典面试题提供的测试用例,对包含 250 个 item 的数组 countries 进行排序、渲染,并计算耗时。结果发现,排序耗时仅用了4ms,而渲染这些List却用了20ms,5倍的差距!而日常开发中,大部分情况下都是,计算的数据更少,而渲染的组件更多。这也就说明了, 大部分情况下,真正的性能瓶颈不是计算,而是渲染。 所以应该把useMemo用在渲染昂贵的组件上,而不是计算上。

那为什么不给所有的组件都使用useMemo呢?前面也说了,缓存会影响项目初始化的速度,而且可能会导致与PureComponent相同的问题,传入子组件的prop浅层并无变化,于是被useMemo包裹的子组件并不会re-render,但其实此时正需要它re-render。

结论

  1. 大部分的 useMemo 和 useCallback 都应该移除,他们可能没有带来任何性能上的优化,反而增加了程序首次渲染的负担,并增加程序的复杂性。
  2. 使用 useMemo 和 useCallback 优化子组件 re-render 时,必须同时满足:
    • 子组件被React.memo 或 useMemo 缓存;
    • 子组件所有的prop都被缓存。
  3. 不推荐默认给所有组件都使用缓存,大量组件初始化时被缓存,可能导致过多的内存消耗,并影响程序初始化渲染的速度。

专栏订阅入口

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发