你应该仅仅把useMemo作为性能优化的手段

文章概叙

本文主要通过几个简单的例子,讲解下useMemo这个hook,给诸君参考,也是给我自己做一个记录

关于useMemo

useMemo是一个React Hook,它在每次重新渲染的时候能够缓存计算的结果。

相比于其他很常用的hook,如useState、useEffect等hook,useMemo算是一个冷门的hook了,当然这必须有useCallback的一份大功劳,至少在我开发的项目中,很少直接使用useMemo,都是使 用useCallback来达成缓存组件的目的,不过这不能说useMemo是一个"过渡性"的东西。

但是无论如何,官网上有一句话我很喜欢,必须贴上来

你应该仅仅把 useMemo 作为性能优化的手段。如果没有它,你的代码就不能正常工作,那么请先找到潜在的问题并修复它。然后再添加 useMemo 以提高性能。

昂贵的计算开销

昂贵的计算开销,在我们开发中的生涯中,首当其冲就是遍历数组了,尤其是遍历一个几千个元素的数组,那可真是跟在LeetCode上做优化一样刺激。。

而useMemo的一个大优点,就是帮助我们跳过昂贵的计算开销,下面的例子会通过一个最简单的SKU来模拟如何使用useMemo。

useMemo语法

使用前,先让我们了解下useMemo的语法先。

javascript 复制代码
useMemo(calculateValue, dependencies)
  • calculateValue

一个纯函数,可以返回任意类型,当我们的组件首次渲染的时候会调用该函数,在后续的渲染中,如果他的dependencies没有变化的时候,React将返回相同的值,否则会计算返回最新结果。

  • dependencies

依赖项,决定函数是否会被执行,是一个可为空的数组,从props、state中拿来用都ok,但是需要知道React会使用Object.js比较参数前后值是否一致。

  • return

在初次渲染时,useMemo 返回不带参数调用 calculateValue 的结果。 否则,根据依赖项是否变动决定是否返回缓存值或者重新计算值。

具体用法如下:

javascript 复制代码
  const total = useMemo(() => {
    return count1 * 10 + count2 * 20;
  }, [count1, count2]);

Demo

首先,来一个最简单的SKU例子。

javascript 复制代码
import {  useState } from "react";
​
export default () => {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);
​
  console.log("渲染页面的次数");
  return (
    <>
      <div>
        <span>商品1:</span>
        <input
          placeholder="商品1的数量"
          defaultValue={count1}
          onBlur={(v: any) => {
            setCount1(v.target.value);
          }}
        />
        <div>商品1的价格为10元</div>
      </div>
      <div>
        <span>商品2:</span>
        <input
          placeholder="商品2的数量"
          defaultValue={count2}
          onBlur={(v: any) => {
            setCount2(v.target.value);
          }}
        />
        <div>商品2的价格为20元</div>
      </div>
      <div style={{ marginTop: "30px" }}>
        总价为:{count1 * 10 + count2 * 20}元
      </div>
    </>
  );
};

​ UI如下:

在页面中,当我们输入框的onBlur事件触发时,我们会去计算一下价钱(当然实际上我们的价格计算不会放在前端去计算,因为安全问题以及优惠卷、商品剩余数量等问题,我们可以放在后台去计算)

秉承着能抽成一个方法就觉得不写成一个变量的原则,我们现在可以将其抽成一个计算价格的函数。

javascript 复制代码
  const calcPrice = () => {
    return count1 * 10 + count2 * 20;
  };
  <div style={{ marginTop: "30px" }}>
  总价为:{calcPrice()}元
  </div>

现在我们将计算价格的方法抽出来了,任务算是完成了,但是我们发现,我们价格的依赖项是count1以及count2,而由于存在用户不会改变数量的情况,所以我们可以用useMemo缓存下价格,即下面的代码:

javascript 复制代码
  const calcPrice = useMemo(() => {
    return count1 * 10 + count2 * 20;
  }, [count1, count2]);
  
  //   const calcPrice = () => {
  //     return count1 * 10 + count2 * 20;
  //   };

但是需要注意,我们使用calcPrice的时候,不能直接调用跟这个方法了,因为他是一个高阶函数,所以要这么用

javascript 复制代码
  <div>总价为:{calcPrice}元</div>

至此,只有当我们的count1跟count2变化的时候,我们的价格才会重新计算一次,而useMemo的语法也算是被我们掌握了~

与useEffect的区别

做多了React开发的各位大佬,肯定也发现了useEffect以及useMemo有一个共同点,都是一个副作用函数,都是根据一个依赖项去执行某个操作。

useEffect以及useMemo还有两个区别。

  • 返回值

useEffect没有返回值,useEffect的本意是根据监听值的变化来重新执行副作用操作。 useMemo是在每次重新渲染的时候可以缓存计算的结果,所以需要将结果返回出去。

  • 触发时机

useMemo是在Dom更新之前触发,直接计算了结果。 useEffect是在Dom更新之后触发,在dom更新之后再次执行某些副作用操作。

如下面例子。当我们用useEffect计算价格时候,我们会这么做。

javascript 复制代码
   const [total, setTotal] = useState(0);
   
  useEffect(() => {
    setTotal(count1 * 10 + count2 * 20);
  }, [count1, count2]);

在使用useEffect的时候,我们监听到count1以及count2变化了(请注意,此时是页面的第一次更新)

接着,我们就使用了setState函数,重新去将计算的结果渲染在了页面上(此前为第二次的渲染)

而当我们使用了useMemo的时候,由于是直接将返回的结果设置在页面上,所以当我们需要计算价格的时候,我们只是调用了一次。

在测试代码时,建议只保留一个商品,这样子能更加明显的看出来。

跳过组件的重新渲染

在上述的例子中,我们使用了useMemo来缓存总价,但是也只是缓存了一个计算好的数字而已,看起来并不酷炫,所以我们一般不会做这么Low的事情,我们会选择将整个显示价格的组件缓存下来。 在实际开发中,我们就经常会这么做,原因无他,即然显示价格的div中变动的因素只有一个价格,那你为啥不整个组件一起缓存呢...代码看起来还好看点。

而上述的缓存组件的说法,我们一般都成为跳过组件的重新渲染,与memo有异曲同工之妙。 最终代码如下

javascript 复制代码
import { useEffect, useMemo, useState } from "react";
​
export default () => {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);
​
  const totalComponent = useMemo(
    () => (
      <div style={{ marginTop: "30px" }}>
        总价为:{count1 * 10 + count2 * 20}元
      </div>
    ),
    [count1, count2]
  );
  console.log("渲染页面的次数");
  return (
    <>
      <div>
        <span>商品1:</span>
        <input
          placeholder="商品1的数量"
          defaultValue={count1}
          onBlur={(v: any) => {
            setCount1(v.target.value);
          }}
        />
        <div>商品1的价格为10元</div>
      </div>
      <div>
        <span>商品2:</span>
        <input
          placeholder="商品2的数量"
          defaultValue={count2}
          onBlur={(v: any) => {
            setCount2(v.target.value);
          }}
        />
        <div>商品2的价格为20元</div>
      </div>
      {totalComponent}
    </>
  );
};
​

总结

文章的最后,本来是想演示下当我们的依赖项中含有一个数组的时候,我们的useMemo会不会因为对象或者数组的引用地址变动了而无法缓存,但是考虑到这种实际情况比较少(在memo中发生的概率大),所以就不写了,等大家遇到了再说。

你应该仅仅把useMemo作为性能优化的手段

个人公众号,求大佬们关注~

公众号文章

预祝大家新年快乐

相关推荐
大表哥63 小时前
在react中 使用redux
前端·react.js·前端框架
因为奋斗超太帅啦5 小时前
React学习笔记(三)——React 组件通讯
笔记·学习·react.js
西瓜本瓜@7 小时前
React + React Image支持图像的各种转换,如圆形、模糊等效果吗?
前端·react.js·前端框架
黄毛火烧雪下7 小时前
React 的 useEffect 钩子,执行一些异步操作来加载基本信息
前端·chrome·react.js
蓝莓味柯基7 小时前
React——点击事件函数调用问题
前端·javascript·react.js
资深前端之路7 小时前
react jsx
前端·react.js·前端框架
白鹭凡12 小时前
react 甘特图之旅
前端·react.js·甘特图
Passion不晚16 小时前
Vue vs React vs Angular 的对比和选择
vue.js·react.js·前端框架·angular.js
光影少年1 天前
usemeno和usecallback区别及使用场景
react.js
吕彬-前端1 天前
使用vite+react+ts+Ant Design开发后台管理项目(二)
前端·react.js·前端框架