memo、useMemo、useCallback总结

memo

类组件可以通过继承类 PureComponent 或者实现 shouldComponentUpdate 来主动判断组件是否需要重新渲染,以此来提高性能。在使用函数组件的时候可以使用memo达到此目的。

  • React.memo 接收两个参数,一个是组件,一个是(比较)函数
    • 组件:组件必须是函数式组件
    • 函数:这个函数就是定义组件是否需要重渲染的钩子,该函数传入两个参数,第一个参数为上次渲染的 props,第二参数为本次渲染的 props
  • 函数返回值为 true 时复用最近一次渲染,否则 false 重新渲染

⚠️ 注意:
如果不通过比较函数进行比较,那么依然是一种对象的浅比较,有复杂对象时无法重新渲染

举个例子

import { useState } from 'react';

const Com1 = (props: any) => {
  console.log(`Com1 ---- ${props.count}`)
  return <div> {props.count}</div>;
};

const Com2 = (props: any) => {
  console.log(`Com2 ---- ${props.count}`)
  return <div> {props.count}</div>
};

const App = () => {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);

  return (
    <>
      <Com1 count={count1} />
      <button onClick={() => setCount1(count1 + 1)}>count1 + 1</button>
      <Com2 count={count2} />
      <button onClick={() => setCount2(count2 + 1)}>count2 + 1</button>
    </>
  );
};
export default () => <App />;

当你点击count2 + 1按钮,控制台打印如下:
在这里插入图片描述

Com1也触发了render

明明点击的是count2 + 1按钮,我只希望修改count2,也只希望Com2组件重新render

不必要的render产生了!

当你使用了memo后:

import { memo, useState } from 'react';

const Com1 = (props: any) => {
  console.log(`Com1 ---- ${props.count}`)
  return <div> {props.count}</div>;
};

const Com2 = (props: any) => {
  console.log(`Com2 ---- ${props.count}`)
  return <div> {props.count}</div>
};

const MemoCom1 = memo(Com1);
const MemoCom2 = memo(Com2);

const App = () => {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);

  return (
    <>
      <MemoCom1 count={count1} />
      <button onClick={() => setCount1(count1 + 1)}>count1 + 1</button>
      <MemoCom2 count={count2} />
      <button onClick={() => setCount2(count2 + 1)}>count2 + 1</button>
    </>
  );
};
export default () => <App />;

点击count2 + 1按钮:

在这里插入图片描述
只有Com2触发了render

避免了Com1组件不必要的渲染 ✅

useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
  • 把 创建 函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算memoized值。这种优化有助于避免在每次渲染时都进行高开销的计算。
  • 记住,传入 useMemo 的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo

⚠️ 注意:

  • 如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。

你可以把 useMemo 作为性能优化的手段,但不要把它当成语义上的保证。将来,React 可能会选择 遗忘 以前的一些 memoized 值,并在下次渲染时重新计算它们,比如为离屏组件释放内存。先编写在没有 useMemo 的情况下也可以执行的代码,之后再在你的代码中添加 useMemo,以达到优化性能的目的。

举个例子

import { memo, useState } from 'react';

const Com1 = (props: any) => {
  console.log(`Com1 ---- ${props.count}`)
  return <div> {props.count}</div>;
};

const Com2 = (props: any) => {
  console.log(`Com2 ---- ${props.count}`)
  return <div> {props.count}</div>
};

const Com3 = (props: any) => {
  console.log(`Com3 ---- ${props.count}`)
  return <div>{props.count}</div>;
};

const MemoCom1 = memo(Com1);
const MemoCom2 = memo(Com2);
const MemoCom3 = memo(Com3);

const App = () => {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);
  const count3 = count1 + Math.random();
  
  return (
    <>
      <MemoCom1 count={count1} />
      <button onClick={() => setCount1(count1 + 1)}>count1 + 1</button>
      <MemoCom2 count={count2} />
      <button onClick={() => setCount2(count2 + 1)}>count2 + 1</button>
      <MemoCom3 count={count3} />
    </>
  );
};
export default () => <App />;

上面的例子中count3只依赖了count1并作为prop传入了MemoCom3组件。

点击count2 + 1按钮:

在这里插入图片描述

Com3也触发了render

我们希望只在count1变化的时候重新计算count3并重新渲染MemoCom3组件。

不必要的render产生了!

当使用useMemo后:

import { memo, useState, useMemo } from 'react';

const Com1 = (props: any) => {
  console.log(`Com1 ---- ${props.count}`)
  return <div> {props.count}</div>;
};

const Com2 = (props: any) => {
  console.log(`Com2 ---- ${props.count}`)
  return <div> {props.count}</div>
};

const Com3 = (props: any) => {
  console.log(`Com3 ---- ${props.count}`)
  return <div>{props.count}</div>;
};

const MemoCom1 = memo(Com1);
const MemoCom2 = memo(Com2);
const MemoCom3 = memo(Com3);

const App = () => {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);
  const memoCount3 = useMemo(() => {
    return count1 * Math.random();
  }, [count1]);
  
  return (
    <>
      <MemoCom1 count={count1} />
      <button onClick={() => setCount1(count1 + 1)}>count1 + 1</button>
      <MemoCom2 count={count2} />
      <button onClick={() => setCount2(count2 + 1)}>count2 + 1</button>
      <MemoCom3 count={memoCount3} />
    </>
  );
};
export default () => <App />;

点击count2 + 1按钮:

在这里插入图片描述
只有Com2触发了render

避免了Com3组件不必要的渲染 ✅

useCallback

const memoizedCallback = useCallback(() => {
  setValue(value);
}, [value]);
  • 返回一个 memoized 回调函数
  • 把 内联回调函数 及 依赖项数组 作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该 回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用 引用相等性 去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用。
  • useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

⚠️ 注意:

  • 依赖项数组不会作为参数传给回调函数。虽然从概念上来说它表现为:所有回调函数中引用的值都应该出现在依赖项数组中。未来编译器会更加智能,届时自动创建数组将成为可能。
  • 推荐启用 eslint-plugin-react-hooks 中的 exhaustive-deps 规则。此规则会在添加错误依赖时发出警告并给出修复建议。

举个例子

import { memo, useState, useCallback, useMemo } from 'react';

const Com1 = (props: any) => {
  console.log(`Com1 ---- ${props.count}`)
  return <div> {props.count}</div>;
};

const Com2 = (props: any) => {
  console.log(`Com2 ---- ${props.count}`)
  return <div> {props.count}</div>
};

const Com3 = (props: any) => {
  console.log(`Com3 ---- ${props.count}`)
  return <div>{props.count}</div>;
};

const Com4 = (props: any) => {
  console.log(`Com4 ---- `)
  return <button onClick={props.handleCount3}>count1 * 2 + 1</button>;
};

const MemoCom1 = memo(Com1);
const MemoCom2 = memo(Com2);
const MemoCom3 = memo(Com3);
const MemoCom4 = memo(Com4);

const App = () => {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);
  const memoCount3 = useMemo(() => {
    return count1 * Math.random();
  }, [count1]);

  const handleCount3 = (e: any) => {
  	console.log('handleCount3', e.target);
	setCount1(count1 + 1);
  }

  return (
    <>
      <MemoCom1 count={count1} />
      <button onClick={() => setCount1(count1 + 1)}>count1 + 1</button>
      <MemoCom2 count={count2} />
      <button onClick={() => setCount2(count2 + 1)}>count2 + 1</button>
      <MemoCom3 count={memoCount3} />
      <MemoCom4 handleCount3={handleCount3} />
    </>
  );
};
export default () => <App />;

上面的例子中handleCount3函数的函数体依赖了count1,作用是修改count1并作为prop传入MemoCom4组件。

点击count2 + 1按钮:

在这里插入图片描述
Com4也触发了render

我们只希望在count1需要改变之前拿到上一个count1,并重新声明handleCount3函数传入MemoCom4组件触发重新渲染。

不必要的render产生了!

使用useCallback后:

import { memo, useState, useCallback, useMemo } from 'react';

const Com1 = (props: any) => {
  console.log(`Com1 ---- ${props.count}`)
  return <div> {props.count}</div>;
};

const Com2 = (props: any) => {
  console.log(`Com2 ---- ${props.count}`)
  return <div> {props.count}</div>
};

const Com3 = (props: any) => {
  console.log(`Com3 ---- ${props.count}`)
  return <div>{props.count}</div>;
};

const Com4 = (props: any) => {
  console.log(`Com4 ---- `)
  return <button onClick={props.handleCount3}>count1 * 2 + 1</button>;
};

const MemoCom1 = memo(Com1);
const MemoCom2 = memo(Com2);
const MemoCom3 = memo(Com3);
const MemoCom4 = memo(Com4);

const App = () => {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);
  const memoCount3 = useMemo(() => {
    return count1 * 2;
  }, [count1]);

  const handleCount3 = (e: any) => {
    console.log('handleCount3', e.target);
    setCount1(count1 + 1);
  };

  const callbackHandleCount3 = useCallback((e) => {
    handleCount3(e);
  }, [count1]);

  return (
    <>
      <MemoCom1 count={count1} />
      <button onClick={() => setCount1(count1 + 1)}>count1 + 1</button>
      <MemoCom2 count={count2} />
      <button onClick={() => setCount2(count2 + 1)}>count2 + 1</button>
      <MemoCom3 count={memoCount3} />
      <MemoCom4 handleCount3={callbackHandleCount3} />
    </>
  );
};
export default () => <App />;

点击count2 + 1按钮:

在这里插入图片描述
只有Com2触发了render

避免了Com4组件不必要的渲染 ✅

总结

memouseMemouseCallback都是react项目性能优化的手段。但是当你编写庞大的组件之前也可以考虑是否有其他方式去减少不必要的渲染,比如状态下移和内提升,感兴趣的可以去看Dan的这篇文章:在你写memo()之前

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值