如何用React.useMemo()进行记忆?
React组件不时地要进行昂贵的计算。例如,给定一个大的雇员列表和一个搜索查询,组件应该通过查询来过滤雇员的名字。
在这种情况下,只要小心,你可以尝试使用备忘录技术来提高组件的性能。
在这篇文章中,我将描述如何以及何时使用useMemo() React挂钩。
1.useMemo() 钩子
useMemo() 是一个内置的React钩子,接受2个参数--一个计算结果的函数compute 和depedencies 数组。
const memoizedResult = useMemo(compute, dependencies);
在初始渲染期间,useMemo(compute, dependencies) 调用compute ,将计算结果备忘化,并将其返回给组件。
如果在接下来的渲染过程中,依赖关系没有变化,那么useMemo() 就不会调用 compute ,而是返回备忘的值。
但是如果在重新渲染的过程中,依赖关系发生了变化,那么useMemo() 就会调用 compute ,记忆新的值,并将其返回。
这就是useMemo() 钩子的本质。
如果你的计算回调使用道具或状态值,那么一定要把这些值作为依赖关系来表示。
const memoizedResult = useMemo(() => {
return expensiveFunction(propA, propB);
}, [propA, propB]);
现在让我们在一个例子中看看useMemo() 是如何工作的。
2.useMemo()- 一个例子
一个组件<CalculateFactorial /> ,计算一个引入到输入字段的数字的阶乘。
下面是<CalculateFactorial /> 组件的一个可能的实现。
import { useState } from 'react';
export function CalculateFactorial() {
const [number, setNumber] = useState(1);
const [inc, setInc] = useState(0);
const factorial = factorialOf(number);
const onChange = event => {
setNumber(Number(event.target.value));
};
const onClick = () => setInc(i => i + 1);
return (
<div>
Factorial of
<input type="number" value={number} onChange={onChange} />
is {factorial}
<button onClick={onClick}>Re-render</button>
</div>
);
}
function factorialOf(n) {
console.log('factorialOf(n) called!');
return n <= 0 ? 1 : n * factorialOf(n - 1);
}
每当你改变输入值时,阶乘的计算factorialOf(n) ,'factorialOf(n) called!' ,并记录到控制台。
在另一边,每次你点击Re-render按钮,inc 状态值就会被更新。更新inc 状态值会触发<CalculateFactorial /> 重新渲染。但是,作为次要的影响,在重新渲染期间,阶乘又被重新计算了--'factorialOf(n) called!' 被记录到控制台。
当组件重新渲染时,你怎样才能将阶乘计算备忘化?欢迎使用useMemo() 钩子!
通过使用useMemo(() => factorialOf(number), [number]) ,而不是简单的factorialOf(number) ,React对阶乘的计算进行了备忘。
让我们改进<CalculateFactorial /> ,将阶乘计算备忘化。
import { useState, useMemo } from 'react';
export function CalculateFactorial() {
const [number, setNumber] = useState(1);
const [inc, setInc] = useState(0);
const factorial = useMemo(() => factorialOf(number), [number]);
const onChange = event => {
setNumber(Number(event.target.value));
};
const onClick = () => setInc(i => i + 1);
return (
<div>
Factorial of
<input type="number" value={number} onChange={onChange} />
is {factorial}
<button onClick={onClick}>Re-render</button>
</div>
);
}
function factorialOf(n) {
console.log('factorialOf(n) called!');
return n <= 0 ? 1 : n * factorialOf(n - 1);
}
打开演示。每次你改变数字的值时,'factorialOf(n) called!' 被记录到控制台。这是预期的。
然而,如果你点击Re-render按钮,'factorialOf(n) called!' 不会被记录到控制台,因为useMemo(() => factorialOf(number), [number]) 会返回备忘的阶乘计算。很好!
3.useMemo()vsuseCallback()
useCallback(),与useMemo() 相比,是一个更专门的钩子,它将回调进行备忘。
import { useCallback } from 'react';
function MyComponent({ prop }) {
const callback = () => {
return 'Result';
};
const memoizedCallback = useCallback(callback, [prop]);
return <ChildComponent callback={memoizedCallback} />;
}
在上面的例子中,只要prop 依赖关系相同,useCallback(() => {...}, [prop]) 就会返回同一个函数实例。
你可以用同样的方式使用useMemo() 来记忆回调。
import { useMemo } from 'react';
function MyComponent({ prop }) {
const callback = () => {
return 'Result';
};
const memoizedCallback = useMemo(() => callback, [prop]);
return <ChildComponent callback={memoizedCallback} />;
}
4.谨慎使用记忆化
虽然useMemo() 可以提高组件的性能,但你必须确保对有钩子和没有钩子的组件进行剖析。只有在这之后,才能得出结论,膜化是否值得。
当备忘化使用不当时,它可能会损害性能。
5.5.结论
useMemo(() => computation(a, b), [a, b]) 钩子可以让你对昂贵的计算进行记忆。考虑到相同的 依赖关系,[a, b]一旦备忘化,钩子就要返回备忘化的值,而不调用 computation(a, b)。