memo、useMemo、useCallback 应用及区别,性能优化

作者: tww844475003 分类: 前端开发 发布时间: 2023-02-11 23:44

当用class组件的时候我们可以通过 PureComponent 或者生命周期中的 shouldComponentUpdate 方法来进行优化,但是对于hooks要怎么做呢?

memo

React.memo 为高阶组件。它与 React.PureComponent 非常相似,但它适用于函数组件,但不适用于 class 组件。

如果你的函数组件在给定相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。

  • MemoCustomCom 会检查 props 是否变化才会重新渲染子组件
  • props (外部常量、或者通过 useState定义)的值改不会导致重新渲染组件
  • 如果props值是在函数内容定义的值为引用类型类型,非基本数据类型,就会导致重新渲染组件
import React, { memo, useState } from 'react';

const types = [
  {
    id: 1,
    name: 'react.js'
  },
  {
    id: 2,
    name: 'vue.js'
  }
]

export default function Performance(props) {
  const [ count, setCount ] = useState(0);
  const [ a2, setA2 ] = useState('hello A');
  const [ obj2, setObj2 ] = useState({
    a: 1,
    b: 2
  });
  const [ arr2, setArr2 ] = useState([1, 2, 3, 4]);

  const add = () => {
    setCount(count + 1);
  }

  const a = 'hello A';
  const obj = {
    a: 1,
    b: 2
  }
  const arr = [1, 2, 3, 4]

  return (
    <div>
      <p>hello Performance!</p>
      <p style={{fontWeight: 'bold'}}>{count}</p>
      <button onClick={add}>点击</button>
      <p>子组件</p>
      <CustomCom />
      <p>memo组件</p>
      <MemoCustomCom value={arr2} />
    </div>
  )
}

function CustomCom(props) {
  console.log('渲染');

  const currentTime = Date.now();
  return (
    <div>
      {currentTime}
    </div>
  )
}

const MemoCustomCom = memo(CustomCom);

React.memo 仅检查 props 变更。如果函数组件被 React.memo 包裹,且其实现中拥有 useState 或 useContext 的 Hook,当 context 发生变化时,它仍会重新渲染。

默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。

function areEqual(prevProps, nextProps) {
  return true;
}

const MemoCustomCom = memo(CustomCom, areEqual);

areEqual 这是不是引 class 组件的 shouldComponentUpdate 一样了

useMemo与useCallback区别

useCallback 可以理解为 useMemo的语法糖。 useCallback((x) => { log(x) }, [m]) 等价于 useMemo(() => { (x) => { log(x) } }, [m])

主要区别是 React.useMemo 将调用 fn 函数并返回其结果,而 React.useCallback 将返回 fn 函数而不调用它。

useCallBackuseMemo
返回值 一个缓存的回调函数一个缓存的值
参数需要缓存的函数,依赖项需要缓存的值(也可以是个计算然后再返回值的函数) ,依赖项
使用场景父组件更新时,通过props传递给子组件的函数也会重新创建,然后这个时候使用 useCallBack 就可以缓存函数不使它重新创建组件更新时,一些计算量很大的值也有可能被重新计算,这个时候就可以使用 useMemo 直接使用上一次缓存的值
  • 当父组件 value 值变化时,没有 useMemo 包裹的方法总是会执行
  • 当父组件 value 值变化时,没有 useCallback 包裹的方法也问题会执行
import React, { useState, useMemo, useCallback, memo } from 'react';

export default function Performance2(props) {
  const [ count, setCount ] = useState(0);
  const [ value, setValue ] = useState(10);

  const getSum = () => {
    console.log('总计执行了');
    return count;
  }

  const getSum2 = useMemo(() => {
    console.log('useMemo 总计执行了');
    return count;
  }, [count])

  const getSum3 = useCallback(() => {
    console.log('useCallback 总计执行了');
    return count;
  }, [count])

  return (
    <div>
      <p>hello performance!</p>
      <p>{count}</p>
      <input value={value} onChange={(e) => setValue(e.target.value)} />
      <button onClick={() => setCount(count + 1)}>加</button>
      <p>数量:{getSum()}</p>
      <p>数量:{getSum2}</p>
      <p>数量:{getSum3()}</p>
      <MemoChild getSum={getSum3} />
    </div>
  )
}

function Child(props) {
  console.log('child 渲染了');

  return (
    <div>{props.getSum()}</div>
  )
}

const MemoChild = memo(Child);

也许这里你发现的 useCallback 示例使用了 memo 包裹组件。是的,你没有看错,因为 useCallback 只缓存的函数,并不代表组件不会重新渲染,他们一般配合使用。

UseCallBack的应用场景

1、将回调函数作为props传递给子组件,可以使用UseCallBack来避免子组件不必要的重新渲染;

2、在使用useEffect时,如果依赖项数组中包含回调函数,可以使用UseCallBack来避免不必要的副作用执行;

3、在使用自定义Hook时,可以使用UseCallBack来优化自定义Hook的回调函数;

总结

  • React.memo 认定两次地址是相同就可以避免子组件冗余的更新
  • useMemo 针对不必要的计算进行优化,避免了当前组件中一些的冗余计算操作
  • useCallBack 针对可能重新创建的函数进行优化,使得函数被缓存
前端开发那点事
微信公众号搜索“前端开发那点事”

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注