useMemo
useMemo 是一个 React Hook,它在每次重新渲染的时候能够缓存计算的结果。
语法
const cachedValue = useMemo(calculateValue, dependencies);
参数
-
calculateValue:要缓存计算值的函数。它应该是一个没有任何参数的纯函数,并且可以返回任意类型。React 将会在首次渲染时调用该函数;在之后的渲染中,如果 dependencies 没有发生变化,React 将直接返回相同值。否则,将会再次调用 calculateValue 并返回最新结果,然后缓存该结果以便下次重复使用。
-
dependencies:所有在 calculateValue 函数中使用的响应式变量组成的数组。响应式变量包括 props、state 和所有你直接在组件中定义的变量和函数。如果你在代码检查工具中 配置了 React,它将会确保每一个响应式数据都被正确地定义为依赖项。依赖项数组的长度必须是固定的并且必须写成 [dep1, dep2, dep3] 这种形式。React 使用 Object.is 将每个依赖项与其之前的值进行比较。
返回值
在初次渲染时,useMemo 返回不带参数调用 calculateValue 的结果。
在接下来的渲染中,如果依赖项没有发生改变,它将返回上次缓存的值;否则将再次调用 calculateValue,并返回最新结果。
用途
- 跳过代价昂贵的重新计算
- 跳过组件的重新渲染
- 记忆另一个 Hook 的依赖
- 记忆一个函数
跳过代价昂贵的重新计算
在组件顶层调用 useMemo 以在重新渲染之间缓存计算结果:
import { useMemo } from "react"; function TodoList({ todos, tab, theme }) { const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]); // ... }
你需要给 useMemo 传递两样东西:
- 一个没有任何参数的 calculation 函数,像这样 () =>,并且返回任何你想要的计算结果。
- 一个由包含在你的组件中并在 calculation 中使用的所有值组成的 依赖列表。 在初次渲染时,你从 useMemo 得到的 值 将会是你的 calculation 函数执行的结果。
在随后的每一次渲染中,React 将会比较前后两次渲染中的 所有依赖项 是否相同。如果通过 Object.is 比较所有依赖项都没有发生变化,那么 useMemo 将会返回之前已经计算过的那个值。否则,React 将会重新执行 calculation 函数并且返回一个新的值。
换言之,useMemo 在多次重新渲染中缓存了 calculation 函数计算的结果直到依赖项的值发生变化。
让我们通过一个示例来看看这在什么情况下是有用的。
默认情况下,React 会在每次重新渲染时重新运行整个组件。例如,如果 TodoList 更新了 state 或从父组件接收到新的 props,filterTodos 函数将会重新运行:
function TodoList({ todos, tab, theme }) { const visibleTodos = filterTodos(todos, tab); // ... }
如果计算速度很快,这将不会产生问题。但是,当正在过滤转换一个大型数组,或者进行一些昂贵的计算,而数据没有改变,那么可能希望跳过这些重复计算。如果 todos 与 tab 都与上次渲染时相同,那么像之前那样将计算函数包装在 useMemo 中,便可以重用已经计算过的 visibleTodos。
这种缓存行为叫做 记忆化。
跳过组件的重新渲染
默认情况下,当一个组件重新渲染时,React 会递归地重新渲染它的所有子组件。但是如果你已经确认重新渲染很慢,你可以通过将子组件包装在 memo 中,这样当它的 props 跟上一次渲染相同的时候它就会跳过本次渲染:
记忆另一个 Hook 的依赖
假设你有一个计算函数依赖于直接在组件主体中创建的对象:
function Dropdown({ allItems, text }) { const searchOptions = { matchMode: "whole-word", text }; const visibleItems = useMemo(() => { return searchItems(allItems, searchOptions); }, [allItems, searchOptions]); // 🚩 提醒:依赖于在组件主体中创建的对象 // ... }
依赖这样的对象会破坏记忆化。当组件重新渲染时,组件主体内的所有代码都会再次运行。创建 searchOptions 对象的代码行也将在每次重新渲染时运行。因为 searchOptions 是你的 useMemo 调用的依赖项,而且每次都不一样,React 知道依赖项是不同的,并且每次都重新计算 searchItems。
要解决此问题,你可以在将其作为依赖项传递之前记忆 searchOptions 对象 本身:
function Dropdown({ allItems, text }) { const searchOptions = useMemo(() => { return { matchMode: "whole-word", text }; }, [text]); // ✅ 只有当 text 改变时才会发生改变 const visibleItems = useMemo(() => { return searchItems(allItems, searchOptions); }, [allItems, searchOptions]); // ✅ 只有当 allItems 或 serachOptions 改变时才会发生改变 // ... }
在上面的例子中,如果 text 没有改变,searchOptions 对象也不会改变。然而,更好的解决方法是将 searchOptions 对象声明移到 useMemo 计算函数的 内部:
记忆一个函数
使用 useCallback