前言:记忆函数
在实践开发中,有一种优化手段叫做记忆函数。
- 例如:我们封装一个方法来实现计算从1到某个整数的总和
function summation(target: number) {
let sum = 0;
for(let i = 1; i <= target; i++) {
sum += i;
}
return sum;
}
- 这个时候,我们思考一个问题,当我们重复调用
summation(100)
时,函数内部的循环计算是不是有点冗余?- 因为传入的参数一样,得到的结果必定也是一样,因此如果传入的参数一致,是不是可以不用再重复计算直接用上次的计算结果返回呢?
- 这时候可以利用闭包能够实现我们的目的
// 初始化一个非正常数字,用于缓存上一次的计算结果
let preTarget = -1;
let memoSum = 0;
export function memoSummation(target: number) {
// 如果传入的参数与上一次一样,直接换回缓存结果
if (preTarget > 0 && preTarget === target) {
return memoSum;
}
console.log('我出现,就表示重新计算了一次');
// 缓存本次传入的参数
preTarget = target;
let sum = 0;
for (let i = 1; i <= target; i++) {
sum += i;
}
// 缓存本次的计算结果
memoSum = sum;
return sum;
}
- 上面的这就是记忆函数。
- 记忆函数利用闭包,在确保返回结果一定正确的情况下,减少了重复冗余的计算过程。
- 这是我们试图利用记忆函数去优化我们代码的目的所在。
两者用法和区别
// fn : 函数,deps:依赖数组
useCallback(fn, deps) 相当于 useMemo(() => fn, deps)
/*
只推荐第三种情况下去使用他们:
1. 无依赖数组时,相当于每次渲染时都会执行fn
2. 依赖数组为空,相当于只执行一次fn
3. 依赖数据非空,依赖项改变时,会重新调用fn
*/
useMemo
缓存计算结果。它接收两个参数:
- 第一个参数为计算过程(回调函数,必须返回一个结果),
- 第二个参数是依赖项(数组),当依赖项中某一个发生变化,结果将会重新计算。
const memoizedValue = React.useMemo(
()=>computeExpensiveValue(a,b),
[a,b]
);
- 返回一个 memoized 值
- useCallback的使用几乎与useMemo一样
- 不过useCallback缓存的是一个函数体,当依赖项中的一项发现变化,函数体会重新创建。
const memoizeCallback = useCallback(
()=>{
doSomething(a,b);
},
[a,b],
);
- 返回一个memoized 函数
示例:
import React, { useMemo, useState, useCallback } from 'react';
import { Button } from 'antd-mobile';
export default function App() {
const [target, setTarget] = useState(0);
const [other, setOther] = useState(0)
const sum = useMemo(() => {
console.log('重新计算一次');
let _sum = 0;
for (let i = 1; i <= target; i++) {
_sum += i;
}
return _sum;
}, [target]);
const inputChange = useCallback((e) => {
console.log(e.target.value);
}, []);
return (
<div style={{ width: '200px', margin: 'auto' }}>
<input type="text" onChange={inputChange} />
<div style={{ width: '80px', margin: '100px auto', fontSize: '40px' }}>{target} {sum}</div>
<Button onClick={() => setTarget(target + 1)}>递增</Button>
<Button onClick={() => setTarget(target - 1)}>递减</Button>
<div style={{ width: '80px', margin: '100px auto', fontSize: '20px' }}>干扰项 {other}</div>
<Button onClick={() => setOther(other + 1)}>递增</Button>
<Button onClick={() => setOther(other - 1)}>递减</Button>
</div>
)
}
注意使用和误区
useMemo/useCallback
的使用非常简单,不过我们需要思考一个问题,使用他们一定能够达到优化的目的吗?
React的学习经常容易陷入过度优化的误区。一些人在得知shouldComponentUpdate
能够优化性能,恨不得每个组件都要用一下,不用就感觉自己的组件有问题。useMemo/useCallback
也是一样。
明白了记忆函数的原理,我们应该知道,记忆函数并非完全没有代价,我们需要创建闭包,占用更多的内存,用以解决计算上的冗余。
而当我们使用useMemo/useCallback
时,由于新增了对于闭包的使用,新增了对于依赖项的比较逻辑,因此,盲目使用它们,甚至可能会让你的组件变得更慢。
大多数情况下,这样的交换,并不划算,或者赚得不多。你的组件可能并不需要使用useMemo/useCallback
来优化。