引言
讲 useMemo 之前,让我们先看这样一个栗子
import {useEffect} from "react"
export function Test(text){
const obj = {a:123, text};
useEffect(()=>{
console.log("被调用");
},[obj])
}
上述代码,
首先,在每次组件 render 的时候,定义的 obj 都是一个全新,与之前不同的对象
其次,obj 作为 useEffect 的依赖项,每次 obj 的变更都会引起 useEffect 的执行
这样每当组件 render 的时候 useEffect 都会被执行
但是通常我们希望 useEffect 仅在 obj 真正变更的时候执行。
如何实现呢?如果有一种 Hook 可以在值不发生变化的情况下直接去拿之前的缓存,而不是新值,这样问题便解决了,而 useMemo 正是可以达到这样的效果!
我们先来看看 useMemo 怎么使用
useMemo的定义
useMemocaches a calculation result between re-renders until its dependencies change.
简单来说,useMemo 在每次重新渲染的时候缓存计算值(参数1返回)直到依赖项(参数2)改变
const cachedValue = useMemo(calculateValue, dependencies)
参数1:计算函数
The function calculates the value that you want to cache
React will call your function during the initial render.On subsequent renders, React will return the same value again if the
dependencieshave not changed since the last render. Otherwise, it will callcalculateValue, return its result, and store it in case it can be reused later.
即该函数就是用来返回缓存值的函数,react 会在初识渲染时调用,在随后的每次渲染中,
- 依赖项改变:调用该函数,将返回值进行 cache
- 依赖项不变:返回返回上一次缓存的值
参数2:依赖项组成的数组
该依赖项值参数1函数中的 reactive values(包括组件中的一些 props,state,变量,函数等),且React 比较依赖项是根据 Object.is 来进行比较的
返回值
-
第一次渲染:返回无参数的参数1函数
-
随后的渲染
- 依赖项改变:重新计算参数1返回值
- 依赖项不变:参数1返回值的缓存
useMemo 的应用场景
1. 引用类型作为useEffect 中的依赖项
知道了 useMemo 的概念和基本使用,我们就可以通过 useMemo 来解决前面提到的问题——引用类型作为useEffect 中的依赖项
import {useEffect, useMemo} from "react"
export function Test(text){
const obj = useMemo(()=>{a:123, text},[text]);
useEffect(()=>{
console.log("被调用");
},[obj])
useMemo 参数一返回要缓存的值 也就是 obj, 这样 text 不变,即使 render 时也会拿 obj 的缓存值,useEffect 也就不会重复执行了
为了加深理解,让我们再看看 useMemo 的其他应用场景
2. 跳过子组件的重新渲染
export default function TodoList({ todos, tab, theme }) {
// ...
return (
<div className={theme}>
<List items={visibleTodos} />
</div>
);
}
比如该栗子,当TodoList 重新渲染时,会导致其中的 List 子组件也进行重新渲染,这是ok的,但是当涉及大量计算花费过多时间时,我们可以通过 memo 缓存该组件,只有当 props 发生变化时才重新渲染
import { memo } from 'react';
const List = memo(function List({ items }) {
// ...
});
Memo 并不总有用,比如下面这个栗子
export default function TodoList({ todos, tab, theme }) {
// Every time the theme changes, this will be a different array...
const visibleTodos = filterTodos(todos, tab);
return (
<div className={theme}>
{/* ... so List's props will never be the same, and it will re-render every time */}
<List items={visibleTodos} />
</div>
);
}
由于每次调用 filterTodos 都会返回一个新的 visibleTodos 数组,作为 List 的参数,这会导致 List 每次都会跟着重新渲染,mome 此时也无济于事,此时可以通过 useMemo 解决该问题
export default function TodoList({ todos, tab, theme }) {
// Tell React to cache your calculation between re-renders...
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab] // ...so as long as these dependencies don't change...
);
return (
<div className={theme}>
{/* ...List will receive the same props and can skip re-rendering */}
<List items={visibleTodos} />
</div>
);
}
3. useMemo 依赖项是组件中直接定义的引用类型
function Dropdown({ allItems, text }) {
const searchOptions = { matchMode: 'whole-word', text };
const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // 🚩 Caution: Dependency on an object created in the component body
// ...
比如该栗子,每次重新渲染的时候 searchOptions 会重新定义一个新的对象,作为 useMemo 的依赖项,会导致每次也重新计算,
- Solve1:将 searchOptions 用 useMemo 缓存
function Dropdown({ allItems, text }) {
const searchOptions = useMemo(()=>{
return { matchMode: 'whole-word', text };
, [text]);
const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // 🚩 Caution: Dependency on an object created in the component body
// ...
- Solve2: 直接把定义 searchOptions 丢到useMemo中
function Dropdown({ allItems, text }) {
const visibleItems = useMemo(() => {
const searchOptions = { matchMode: 'whole-word', text };
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // 🚩 Caution: Dependency on an object created in the component body
// ...
这样 searchOptions 只会在 useMemo 初次调用时定义。
4. 缓存函数--引入 useCallback
export default function ProductPage({ productId, referrer }) {
function handleSubmit(orderDetails) {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
}
return <Form onSubmit={handleSubmit} />;
}
就像 {} 每次会创建一个新的对象,函数定义function() {} 以及 () => {} 也是同样会创建不同的函数,对于上面的栗子,传入的 handleSumit 函数在组件重新渲染的时候会被重新创建一个新的,如果想使 Form 组件缓存,便需要 useMemo 解决
export default function ProductPage({ productId, referrer }) {
useMemo()=>{
return (orderDetails)=> {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
}, [productId ,referrer ])
return <Form onSubmit={handleSubmit} />;
}
这种写法比较冗余且这种缓存函数的需求比较常见,由此引入了 useCallback
export default function Page({ productId, referrer }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + product.id + '/buy', {
referrer,
orderDetails
});
}, [productId, referrer]);
return <Form onSubmit={handleSubmit} />;
}
两种写法完全等价,只是后者减少了嵌套,更简便
useCallback 和 useMemo 的区别
useCallback 和 useMemo 的区别就在于参数1
useMemo参数1 是一个函数 返回值是要缓存的值useCallback参数1 也是一个函数 不过函数本身就是要缓存的值
小结
- 使用 useMemo ,其依赖项不变化则直接返回缓存值
- useCallback 的本质和 useMemo 相同,只是为了更方便地缓存函数
参考资料
本文章初心在于建立个人知识体系,进一步掌握知识,如果能帮到你,那就更好不过了,另外,如果文章有不对的地方,欢迎你指正或私信交流哦~