你可能已经注意到 React Hook 中有一个名为 useMemo
的奇怪的钩子。这个奇怪的钩子意味着什么,它的作用是什么?重要的是,它是怎样为你提供帮助的?
首先,稍微回顾一下 JavaScript 的相等性。
引用比较
你可能还记得 Javascript 如何比较对象?。当我们进行相等性比较时,会有一些棘手的结果:
{} === {} // false
const z = {}
z === z // true
React 用 Object.is
来比较组件,但是得到的结果与使用 ===
相似。所以当 React
检查组件中的改变时,它可能会发现一些我们不会真正考虑的东西。
() => {} === () => {} // false
[] === [] // false
这种比较检查将会导致某些预期之外的 React 重新渲染。如果重新渲染是一些代价高昂的操作,则可能会降低性能。如果一部分需要进行重新渲染,则它将重新渲染整个组件树。因此 React 发布了 memo 来解决这个问题。
Memoization
有一个非常花哨的术语 memoization 。memoization 是一种“优化技术”,它传递了一个复杂的函数来进行记忆。在 memoization 中,当随后传递的参数相同时,它会记住结果。例如有一个计算 1 + 1 的函数,它将返回结果 2。但是如果它使用 memoization,则下次再通过该函数运行 1 + 1 时,它不会再次进行运算,而只会记住答案是 2,从而无需执行加法函数。
在 React 中,memoization 可以优化我们的组件,避免在不需要时进行复杂的重新渲染。例如可以用 React.memo
对程序进行优化,它就像一个纯组件一样,可以包装你的组件。但是 useMemo
的用法有些不同。
在官方的React文档中,useMemo
是这样子的:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useMemo
接受一个函数和一个依赖关系列表(数组 [a,b]
)。它们的行为类似于函数中的参数。依赖关系列表是 useMemo
要去监视的元素:如果没有改变,那么函数的结果将会保持不变,否则它将重新运行这个函数。假如它们没有改变的话,那么重新渲染整个组件也没关系,该函数不会被重新执行,而是直接返回存储的结果。如果包装的函数很大且很运行代价高昂,那么这绝对是一个非常好的方案。这就是 useMemo
的主要用途。
useMemo 示例
const List = useMemo(
() =>
listOfItems.map(item => ({
...item,
itemProp1: expensiveFunction(props.first),
itemProp2: anotherPriceyFunction(props.second)
})),
[listOfItems]
)
在上面的例子中,useMemo
函数将在第一个渲染器上运行。它会阻塞线程,直到函数执行完毕,因为 useMemo
在渲染器中运行。它看起来不如 useEffect
干净,因为 useEffect
可以渲染加载微调器,直到运行代价高昂的函数完成并且效果消失为止。但是如果 listOfItems
从未被改变,那么函数将永远不会再次触发,仍然会获取返回值。这样会使这些函数的执行速度显得很快。这是你在执行高耗时的同步函数时的理想选择。
防止重新渲染
如果你熟悉 React 的类组件生命周期 Hook shouldComponentUpdate,useMemo
在防止不必要的重新渲染方面也有类似用法。假设有以下代码:
function BabyGator({fish, insects}) {
const dinner = {fish, insects};
useEffect(() => {
eatFunction(dinner);
}, [fish, insects])
}
function MamaGator() {
return <BabyGator fish='small edible fish' insects={15}>
}
它工作得很好。useEffect
hook 监视传入的 fish 和 insects。但是这仅适用于 primitive 值。这是关键。
还记得前面提到的“引用比较”吗: [] === [] // false
。这正是 useMemo
和 useCallback
之类的记忆 hook 所做的事。如果的 insects 是一个数组,我们可以把它放在 useMemo
hook 中,在渲染之后,它将相等地引用它。如果一个函数或另一个非原始值位于 useEffect
依赖项中,由于closure 的原因,它将会重新创建一个新数组,并且发现它不相等。
很显然,如果我们只是想存储数组就不需要 useMemo
。但是如果有一个代价高昂的函数来计算这个数组,useMemo
是很有用的。
什么时候不能用 useMemo
useCallback
类似于 useMemo
,但是它返回一个被记忆的函数,而 useMemo
有一个返回 value 的函数。如果依赖项数组为空,则不可能进行记忆,它将在每个渲染器上去计算新的值。在这时你最好实现 useRef
钩子。如果依赖项发生更改,则 useMemo
比 useRef
优秀的一点是能够重新进行存储。
在实现 useMemo
时,你需要问问自己:“这真的是一个代价高昂的函数吗?” 代价高昂意味着它正在消耗大量资源(如内存)。如果在渲染时在函数中定义大量变量,则用 useMemo
进行记忆是非常有意义的。
如果你不希望 useMemo
去触发有副作用的操作或是异步调用。使用 useEffect
中会更有意义。
当你想要使用 useMemo
时,请先编写代码,然后再检查是否可以对其进行优化。不要一开始就去使用 useMemo
开头。这样可能会在小型应用中导致性能变差。
让你的 React 快速进阶的福利
成功的路上并不拥挤,因为嘴上说努力的人很多,真正去做的人少之又少。
就像你可能经常会领取到【海量前端资料包】,收藏起来就再也没看过。
在这样一个信息爆炸、知识唾手可得的时代,我们一定要做个明白人,懂得筛选和判断优质内容。
所以今天,我们想给你点真正有品质的内容 —— React 高级玩家必会指南
本次React专题课深度讲解 React 项目的性能优化、React Hooks 实践指南、React 大厂面试真题等 React 进阶必备指南
除以上课程内容外,还将有每日打卡、抽奖惊喜、微信群答疑等课程服务,真正带你克服惰性,实现 React 进阶 !
它将带你学到什么?
1.React 项目的性能优化实践
大厂面试问React项目优化时的各种讲解,性能指标的各种优化逻辑
核心工程化知识点讲解
不同的核心优化方案剖析,首屏渲染、预加载、ssr等内部逻辑
常考React知识点串讲
2.React Hooks 实践指南
Hooks原理剖析
Hooks产生原因
项目结合Hooks的优化实践
大厂Hooks相关面试题
3. React 相关面试题精讲
深入内部串讲React各个相关面试题
React render渲染逻辑
createElement如何实现虚拟dom
内部核心原理串讲