react hook之useMemo

useMemo的作用

Pass a “create” function and an array of dependencies. useMemo will
only recompute the memoized value when one of the dependencies has
changed.

传递一个创建函数和依赖项,创建函数需要一个返回值,只有在依赖项改变的时候才重新调用此函数,返回一个新的值。
useMemo 也能针对传入子组件的值进行缓存优化
简而言之,useMemo是用来缓存计算属性的。

那么什么是计算属性?

计算属性其实就是函数的返回值,或者说那些需要一个返回值的函数。
在函数中,有些需要我们手动点击去完成一些动作的触发,比如点击打开弹窗,点击按钮变色等等,而有些函数则是直接在渲染的时候就执行了,在dom区域被当做属性值一样去使用,比如计算1+1将这个计算结果展示在界面上,这就是计算属性。计算属性最后一定会用一个return返回一个值

const TestComponent = () => {
    const [count1,setCount1] = useState(0);
    const [count2,setCount2] = useState(0);
    
    //这种是需要我们手动去调用的函数
    const handleFun1 = () => {
        console.log("我需要手动调用,你不点击我不执行");
        setCount1(count + 1);
    } 
    
    //这种被称为计算属性,不需要手动调用,在渲染阶段就会执行的。
    const computedFun2 = () => {
        console.log('我又执行计算了');
        return count2;
    }
    
    return <div onClick = {handleFun1}>
        //每次重新渲染的时候我就会执行
        <span>计算结果: {computedFun2()}</span>
    </div>
}

在上面的代码示例中,computedFun2函数就是一个计算属性。而handleFun1则是一个普通函数。

看上面的例子,组件每次点击执行handleFun1的时候,因为组件内部状态(count1)的改变会让该组件重新渲染而每次组件重新渲染都会让我们去执行computedFun2函数(计算属性),尽管computedFun2函数中只使用到了count2状态,与被改变的状态并没有任何关系。
如果computedFun2函数里面的计算过程非常的复杂,那么每次重新计算无疑的非常麻烦的。而且在我们上面的例子中,我们返回的值并不会因为count1的改变而产生变化。

那么,我们要如何让组件在改变与计算属性无关的状态的时候进行的渲染不触发我们计算属性的重新计算呢?
useMemo就是解决这个问题的。useMemo有两个入参,第一个值填写我们需要缓存的计算属性,第二个值填写依赖,像useCaallback一样,useMemo会在每次需要重新计算的时候去比较依赖是否被更改,只有当依赖改变了被useMemo保护的函数才会重新执行,返回新的值,否则拒绝重新执行,直接返回旧的计算属性值。
将上面HandleFun2方法改变成如下:

const computedFun2 = useMemo(() => {
        console.log('我又执行计算了');
        return count2;
    },[count2])

原来的computedFun2函数和新的computedFun2函数的区别就是否被useMemo包裹。
使用useMemo包裹了computedFun2这个函数之后,computedFun2函数只会在组件初始化的时候和依赖项状态改变(count2)的时候执行,然后不论count2这个状态如何的改变,computedFun2函数都不会再去执行

useMemo使用场景

useMemo不是用得越多越好

useMemo的作用就是缓存,但是缓存都需要成本,所以useMemo这类钩子并不是用得越多越好!大量的使用反而可能导致反向优化。
所有被useMemo保护的函数都会被加入useMemo的工作队列,在组件进行渲染并且此组件内使用了useMemo之后,为了校验改组件内被useMemo包裹的这个计算属性是否需要重新计算,有两个步骤需要做:

  • 先去useMemo的工作队列中找到这个函数
  • 校验这个函数都依赖是否被更改
    这两个步骤都需要成本。当我们大量的使用useMemo之后,非但不能给项目带来性能上的优化,反而会为项目增加负担。

何时使用?

需要复杂计算的时候

比如只是简单那的计算变量的加减乘除之类的,就没必要使用useMemo,而如果是循环处理等情况则使用useMemo才是正向的。比如:
//这种就是完全没必要被useMemo缓存的,计算过程一共也就一个创建变量,一个加一,缓存它反而亏本

// 这个方法只是进行变量加1
const computedFun1 = () => {
    let number = 0;
    number = numebr +1;
    return number;
}

//这个就需要缓存一下了,毕竟他每次计算的计算量还是蛮大的。
//缓存起来,当需要它执行的时候才执行,避免每次渲染都执行。
const computedFun2 = () => {
    let number =  0;
    for(let i=0;i<100000;++i){
        number = number +i-(number-i*1.1);
    }
    return number;
}
当子组件依赖父组件的某一个依赖计算属性并且子组件使用了React.memo进行优化了的时候

React.memo()是通过校验props中的数据是否改变来决定组件是否需要重新渲染的一种缓存技术,也就是说React.memo()其实是通过校验Props中的数据的内存地址是否改变来决定组件是否重新渲染组件的一种技术。
假设我们往子组件传入一个计算属性,当父组件的其他State(与子组件无关的state)改变的时候。那么,因为状态的改变,父组件需要重新渲染,那被React.memo保护的子组件是否会被重新构建?

import {useMemo,memo} from 'react';
const Parent = () => {
    const [parentState,setParentState] = useState(0);  //父组件的state
    
    //需要传入子组件的函数
    const toChildComputed = () => {
        console.log("需要传入子组件的计算属性");
        return 1000;
    }
    
    return (<div>
          <Button onClick={() => setParentState(val => val+1)}>
              点击我改变父组件中与Child组件无关的state
          </Button>
          //将父组件的函数传入子组件
          <Child computedParams={toChildComputed()}></Child>
    <div>)
}

/**被memo保护的子组件**/
const Child = memo(() => {
    consolo.log("我被打印了就说明子组件重新构建了")
    return <div><div>
})

React.memo检测的是props中数据的栈地址是否改变。而父组件重新构建的时候,如果不缓存计算属性,那么计算属性将会被重新计算执行,并返回一个新的值(这意味这返回了一个新的存储地址),新的计算属性传入到子组件中被props检测到栈地址更新。也就引发了子组件的重新渲染。
所以,在上面的代码示例里面,子组件是要被重新渲染的

将toChildComputed改一下:

const toChildComputed = useMemo(() => {
       console.log("需要传入子组件的计算属性");
       return 1000;
    },[])

这样toChildComputed用useMemo包裹,useMemo会在发现依赖没有变化之后返回旧的计算属性值这样的话,父组件重新渲染,子组件中的函数就会因为被useMemo保护而返回旧的计算属性值,子组件就不会检测成地址变化,也就不会重选渲染了。

Tips

事实上在使用中 useMemo 的场景远比 useCallback 要广泛的很多,我们可以将 useMemo 的返回值定义为返回一个函数这样就可以变通的实现了 useCallback。在开发中当我们有部分变量改变时会影响到多个地方的更新那我们就可以返回一个对象或者数组,通过解构赋值的方式来实现同时对多个数据的缓存

const [age, followUser] = useMemo(() => {
  return [
    new Date().getFullYear() - userInfo.birth, // 根据生日计算年龄
    async () => { // 关注用户
      await request('/follow', { uid: userInfo.id });
      // ...
    }
  ];
}, [userInfo]);

return (
  <div>
    <span>name: {userInfo.name}</span>
    <span>age: {age}</span>
    <Card followUser={followUser}/>
    {
      useMemo(() => (
        // 如果 Card1 组件内部没有使用 React.memo 函数,那还可以通过这种方式在父组件减少子组件的渲染
        <Card1 followUser={followUser}/>
      ), [followUser])
    }
  </div>
)

与useCallback相比:
useCallback 与 useMemo 一个缓存的是函数,一个缓存的是函数的返回值。useCallback 是来优化子组件的,防止子组件的重复渲染。useMemo 可以优化当前组件也可以优化子组件,优化当前组件主要是通过 memoize 来将一些复杂的计算逻辑进行缓存。当然如果只是进行一些简单的计算也没必要使用 useMemo,这里可以考虑一些计算的性能消耗和比较 inputs 的性能消耗来做一个权衡。

总结

  • useMemo是用来缓存计算属性的,它会在发现依赖未发生改变的情况下返回旧的计算属性值的地址。
  • useMemo绝不是用的越多越好,缓存这项技术本身也需要成本。
  • useMemo的使用场景之一是:只需要给拥有巨大计算量的计算属性缓存即可。
  • useMemo的另一个使用场景是:当有计算属性被传入子组件,并且子组件使用了react.memo进行了缓存的时候,为了避免子组件不必要的渲染时使用
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值