文章概叙
本文主要通过几个简单的例子,讲解下useMemo这个hook,给诸君参考,也是给我自己做一个记录
关于useMemo
useMemo是一个React Hook,它在每次重新渲染的时候能够缓存计算的结果。
相比于其他很常用的hook,如useState、useEffect等hook,useMemo算是一个冷门的hook了,当然这必须有useCallback的一份大功劳,至少在我开发的项目中,很少直接使用useMemo,都是使
用useCallback来达成缓存组件的目的,不过这不能说useMemo是一个"过渡性"的东西。
但是无论如何,官网上有一句话我很喜欢,必须贴上来
你应该仅仅把 useMemo 作为性能优化的手段。如果没有它,你的代码就不能正常工作,那么请先找到潜在的问题并修复它。然后再添加
useMemo 以提高性能。
昂贵的计算开销
昂贵的计算开销,在我们开发中的生涯中,首当其冲就是遍历数组了,尤其是遍历一个几千个元素的数组,那可真是跟在LeetCode上做优化一样刺激。。
而useMemo的一个大优点,就是帮助我们跳过昂贵的计算开销,下面的例子会通过一个最简单的SKU来模拟如何使用useMemo。
useMemo语法
使用前,先让我们了解下useMemo的语法先。
useMemo(calculateValue, dependencies)
- calculateValue
一个纯函数,可以返回任意类型,当我们的组件首次渲染的时候会调用该函数,在后续的渲染中,如果他的dependencies没有变化的时候,React将返回相同的值,否则会计算返回最新结果。
- dependencies
依赖项,决定函数是否会被执行,是一个可为空的数组,从props、state中拿来用都ok,但是需要知道React会使用Object.js比较参数前后值是否一致。
- return
在初次渲染时,useMemo 返回不带参数调用 calculateValue 的结果。
否则,根据依赖项是否变动决定是否返回缓存值或者重新计算值。
具体用法如下:
const total = useMemo(() => {
return count1 * 10 + count2 * 20;
}, [count1, count2]);
Demo
首先,来一个最简单的SKU例子。
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事件触发时,我们会去计算一下价钱(当然实际上我们的价格计算不会放在前端去计算,因为安全问题以及优惠卷、商品剩余数量等问题,我们可以放在后台去计算)
秉承着能抽成一个方法就觉得不写成一个变量的原则,我们现在可以将其抽成一个计算价格的函数。
const calcPrice = () => {
return count1 * 10 + count2 * 20;
};
<div style={{ marginTop: "30px" }}>
总价为:{calcPrice()}元
</div>
现在我们将计算价格的方法抽出来了,任务算是完成了,但是我们发现,我们价格的依赖项是count1以及count2,而由于存在用户不会改变数量的情况,所以我们可以用useMemo缓存下价格,即下面的代码:
const calcPrice = useMemo(() => {
return count1 * 10 + count2 * 20;
}, [count1, count2]);
// const calcPrice = () => {
// return count1 * 10 + count2 * 20;
// };
但是需要注意,我们使用calcPrice的时候,不能直接调用跟这个方法了,因为他是一个高阶函数,所以要这么用
<div>总价为:{calcPrice}元</div>
至此,只有当我们的count1跟count2变化的时候,我们的价格才会重新计算一次,而useMemo的语法也算是被我们掌握了~
与useEffect的区别
做多了React开发的各位大佬,肯定也发现了useEffect以及useMemo有一个共同点,都是一个副作用函数,都是根据一个依赖项去执行某个操作。
useEffect以及useMemo还有两个区别。
- 返回值
useEffect没有返回值,useEffect的本意是根据监听值的变化来重新执行副作用操作。
useMemo是在每次重新渲染的时候可以缓存计算的结果,所以需要将结果返回出去。
- 触发时机
useMemo是在Dom更新之前触发,直接计算了结果。
useEffect是在Dom更新之后触发,在dom更新之后再次执行某些副作用操作。
如下面例子。当我们用useEffect计算价格时候,我们会这么做。
const [total, setTotal] = useState(0);
useEffect(() => {
setTotal(count1 * 10 + count2 * 20);
}, [count1, count2]);
在使用useEffect的时候,我们监听到count1以及count2变化了(请注意,此时是页面的第一次更新)
接着,我们就使用了setState函数,重新去将计算的结果渲染在了页面上(此前为第二次的渲染)
而当我们使用了useMemo的时候,由于是直接将返回的结果设置在页面上,所以当我们需要计算价格的时候,我们只是调用了一次。
在测试代码时,建议只保留一个商品,这样子能更加明显的看出来。
跳过组件的重新渲染
在上述的例子中,我们使用了useMemo来缓存总价,但是也只是缓存了一个计算好的数字而已,看起来并不酷炫,所以我们一般不会做这么Low的事情,我们会选择将整个显示价格的组件缓存下来。
在实际开发中,我们就经常会这么做,原因无他,即然显示价格的div中变动的因素只有一个价格,那你为啥不整个组件一起缓存呢…代码看起来还好看点。
而上述的缓存组件的说法,我们一般都成为跳过组件的重新渲染,与memo有异曲同工之妙。
最终代码如下
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作为性能优化的手段
个人公众号,求大佬们关注~
公众号文章
预祝大家新年快乐