React Native 中的 useMemo hook 可以在组件渲染之间记忆化计算结果,防止重复执行昂贵的操作。与 useCallback 记忆化函数不同,useMemo 保存返回值,并且仅在指定的依赖项发生更改时重新计算。根据 React Docs, 2024,记忆化对于大型数组的计算和复杂的数据转换特别有效。在 React Native 中,每次额外的重新渲染都会给 JavaScript 和原生线程之间的桥接连接带来额外的负载,因此正确使用 useMemo 直接影响动画的流畅性和界面的响应速度。
要点
useMemo — 是来自 React 标准库的 hook,在 React Native 中无需额外包即可使用。它记忆化传递函数返回的值,并在后续渲染中重用该值,直到数组中指定的依赖项发生更改。
该 hook 的名称来自英语 memoization — 一种优化技术,其中昂贵函数的结果被存储在缓存中。当使用相同参数重复调用时,返回存储的值而不是重新计算。
在 React Native 的上下文中,由于架构原因,记忆化尤为重要。组件的每次重新渲染都会通过 JavaScript — 原生桥接发送数据,这需要时间。如果组件包含昂贵的计算(列表过滤、排序、格式化),每次重新渲染都会重复这些计算,阻塞 JS 线程并导致丢帧。
根据 React Native Performance Docs, 2024,瓶颈通常不是原生组件的渲染,而是渲染之间 JavaScript 逻辑的执行。useMemo 正是为了解决这个问题。
useMemo 接受一个函数和一个依赖项数组,返回记忆化的值。React 存储先前的值和依赖项数组。在每次渲染时,React 使用 Object.is 将当前依赖项与先前的依赖项进行比较。如果至少有一个依赖项发生了变化,函数将重新执行并保存结果。
useMemo 的签名在 Web 和 React Native 中完全相同。第一个参数 — 一个无参数函数,返回要记忆化的值。第二个 — 依赖项数组,当这些依赖项发生变化时,值将被重新计算。
import React, { useMemo } from 'react';
const sortedList = useMemo(() => {
return items.sort((a, b) => a.name.localeCompare(b.name));
}, [items]);
在此示例中,sortedList 仅在 items 数组的引用发生更改时才重新计算。如果数组内容发生更改但引用保持不变(突变),useMemo 将不会看到更改 — 这是一个重要的限制。
React 使用 Object.is 算法来比较依赖项,该算法类似于严格相等 ===,但正确处理 NaN(+0 和 -0 被视为不同)。这意味着对于原始类型(字符串、数字、布尔值),比较按值进行,而对于对象和数组,则按引用进行。
// Object.is({'a': 1}, {'a': 1}) -> false (不同引用)
// Object.is(42, 42) -> true (原始值按值比较)
// Object.is(NaN, NaN) -> true (正确处理 NaN)
理解这一机制至关重要:如果在每次渲染时将新对象传递给依赖项,记忆化将毫无用处,因为依赖项每次都会被视作已更改。
useMemo 在三种情况下是合理的:昂贵的计算、向子组件传递稳定的 props 以及保持对象的引用标识。在 React Native 中,由于移动应用的架构特点,这三种情况经常出现。
如果组件执行大型数据数组的处理(过滤、排序、分组)或资源密集型数学操作,useMemo 可防止每次重新渲染时重复这些计算。在移动应用中,典型的例子是在列表显示之前格式化 API 数据。
当子组件被包裹在 React.memo 中时,它仅在 props 发生更改时才重新渲染。如果 props 对象是在父组件内部没有 useMemo 的情况下创建的,那么每次父组件渲染时都会创建一个新的引用,子组件将重新渲染,从而使 React.memo 的好处化为乌有。
在 React Native 中,样式通常取决于 props — 例如,元素的宽度根据屏幕尺寸进行计算。useMemo 允许仅在输入参数发生变化时计算这些动态样式。
useMemo 不是万能的优化工具。在某些情况下,它不会带来好处,甚至可能由于存储和比较依赖项的开销而降低性能。在 React Native 中,每个字节的内存都很重要,不测量效果就盲目使用 useMemo 是一种反模式。
该 hook 在三种主要情况下没有益处。首先,如果计算是微不足道的(简单的加法、字符串拼接),useMemo 的成本超过了计算本身的价值。其次,如果依赖项每次渲染都会更改 — 函数仍会执行,记忆化只会增加额外的工作。第三,如果组件很少渲染,缓存存储的开销是不合理的。
根据 Kent C. Dodds, 2023,在添加 useMemo 之前,需要使用 React DevTools Profiler 或 React Native 内置的 Performance Monitor 来测量问题。如果组件的重新渲染时间少于 1 毫秒,则不需要记忆化。
useMemo 和 useCallback — 来自 React 的两个记忆化 hook,经常被混淆。区别在于它们保存什么:useMemo 返回函数执行的结果(任何值),而 useCallback 返回函数本身。两者都接受一个依赖项数组,但语法不同。
| 特性 | useMemo | useCallback |
|---|---|---|
| 返回 | 函数执行的结果 | 对函数的引用 |
| 第一个参数 | 返回值的函数 | 要记忆化的函数 |
| 典型用途 | 缓存计算 | 为子组件提供稳定的回调 |
| 等价性 | useMemo(() => fn, deps) | useCallback(fn, deps) === useMemo(() => fn, deps) |
在实践中,useCallback 是 useMemo 之上的语法糖,用于需要记忆化函数的情况。在 React Native 中,两个 hook 的使用频率相同:useMemo — 用于数据(格式化文本、计算样式),useCallback — 用于事件处理程序(onPress、onChangeText)。
让我们来看一下 React Native 应用中使用 useMemo 的两个真实场景。第一个 — 根据搜索查询过滤联系人列表,第二个 — 根据屏幕尺寸计算动态样式。
在此示例中,useMemo 可防止在每次渲染时重新过滤大型联系人列表,如果搜索查询和源数据都没有更改的话。
const ContactList = ({ contacts, searchQuery }) => {
const filteredContacts = useMemo(() => {
if (!searchQuery.trim()) return contacts;
const query = searchQuery.toLowerCase();
return contacts.filter((c) =>
c.name.toLowerCase().includes(query) ||
c.phone.includes(query)
);
}, [contacts, searchQuery]);
return (
<FlatList
data={filteredContacts}
renderItem={renderContact}
keyExtractor={(item) => item.id}
/>
);
};
没有 useMemo,过滤操作将在每次渲染时执行,包括由动画或组件其他部分状态更改引起的渲染。使用 useMemo,过滤操作仅在 contacts 或 searchQuery 更改时触发。
在 React Native 中,样式通常取决于屏幕尺寸或组件的 props。useMemo 允许一次性计算这样的样式对象,并在依赖项发生更改之前重复使用它。
const Card = ({ title, isSelected }) => {
const cardStyle = useMemo(() => ({
backgroundColor: isSelected ? '#e3f2fd' : '#ffffff',
borderWidth: isSelected ? 2 : 1,
padding: 16,
}), [isSelected]);
return (
<View style={cardStyle}>
<Text>{title}</Text>
</View>
);
};
这种方法确保 cardStyle 对象在 isSelected 更改之前保持稳定(相同引用)。如果 Card 被包裹在 React.memo 中,这种模式可以防止子元素的不必要重新渲染。
常见问题
useMemo 返回记忆化的值 — 作为第一个参数传递的函数执行的结果。在后续渲染中,如果依赖项没有更改,React 返回存储的值。
useMemo 返回函数执行的结果(任何值:数字、对象、JSX),而 useCallback 返回函数本身。此外,useCallback(fn, deps) 等价于 useMemo(() => fn, deps)。
useMemo 在微不足道的计算(加法、拼接)、罕见的重新渲染、每次渲染都变化的依赖项以及原始类型的情况下是无效的。在添加 useMemo 之前始终要测量性能。
是的,如果不需要使用的话。React 将先前的值存储在内存中,并在每次渲染时执行依赖项比较。对于简单的计算,这些操作的开销超过了记忆化的收益。
使用 React DevTools Profiler 来测量组件的渲染时间。在 React Native 中,还可以通过开发者菜单使用 Performance Monitor。如果重新渲染时间少于 1 毫秒 — 则不需要 useMemo。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。