2024-08-27 10:10:05 +08:00

160 lines
5.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# useCallback和useMemo
> 面试题useCallback 和 useMemo 的区别是什么?
## useCallback
useCallback 用法如下:
```js
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
```
使用 useCallback 最终会得到一个缓存的函数,该缓存函数会在 a 或者 b 依赖项发生变化时再更新。
**mount 阶段**
在 mount 阶段执行的就是 mountCallback相关代码如下
```js
function mountCallback(callback, deps) {
// 首先还是创建一个 hook 对象
const hook = mountWorkInProgressHook();
// 依赖项
const nextDeps = deps === undefined ? null : deps;
// 把要缓存的函数和依赖数组存储到 hook 对象上
hook.memoizedState = [callback, nextDeps];
// 向外部返回缓存函数
return callback;
}
```
在上面的代码中,首先会调用 mountWorkInProgressHook 得到一个 hook 对象,在 hook 对象的 memoizedState 上面保存 callback 以及依赖项目,最后向外部返回 callback
**update阶段**
update 调用的是 updateCallback相关代码如下
```js
function updateCallback(callback, deps) {
// 拿到之前的 hook 对象
const hook = updateWorkInProgressHook();
// 新的依赖项
const nextDeps = deps === undefined ? null : deps;
// 之前的值,也就是 [callback, nextDeps]
const prevState = hook.memoizedState;
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps = prevState[1]; // 拿到之前的依赖项
// 对比依赖项是否相同
if (areHookInputsEqual(nextDeps, prevDeps)) {
// 相同返回 callback
return prevState[0];
}
}
}
// 否则重新缓存
hook.memoizedState = [callback, nextDeps];
return callback;
}
```
在组件更新阶段,首先拿到之前的 hook 对象,从之前的 hook 对象的 memoizedState 上面拿到之前的依赖项,和新传入的依赖项做一个对比,如果相同,则返回之前缓存的 callback否则就重新缓存返回新的 callback
## useMemo
用法如下:
```js
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
```
使用 useMemo 缓存的是一个值,这个值会在 a 或者 b 发生变化的时候重新进行计算并缓存。
**mount 阶段**
mount 阶段调用的是 mountMemo代码如下
```js
function mountMemo(nextCreate, deps) {
// 创建 hook 对象
const hook = mountWorkInProgressHook();
// 存储依赖项
const nextDeps = deps === undefined ? null : deps;
// ...
// 执行传入的函数,拿到返回值
const nextValue = nextCreate();
// 将函数返回值和依赖存储到 memoizedState 上面
hook.memoizedState = [nextValue, nextDeps];
// 返回函数计算得到的值
return nextValue;
}
```
在 mount 阶段首先会调用 mountWorkInProgressHook 方法得到一个 hook 对象,之后执行传入的函数(第一个参数)得到计算值,将计算值和依赖项目存储到 hook 对象的 memoizedState 上面,最后向外部返回计算得到的值。
**update 阶段**
update 阶段调用的是 updateMemo相关代码如下
```js
function updateMemo(nextCreate, deps) {
// 获取之前的 hook 对象
const hook = updateWorkInProgressHook();
// 新的依赖项
const nextDeps = deps === undefined ? null : deps;
// 获取之前的 memoizedState也就是 [nextValue, nextDeps]
const prevState = hook.memoizedState;
if (prevState !== null) {
// Assume these are defined. If they're not, areHookInputsEqual will warn.
if (nextDeps !== null) {
// 拿到之前的依赖项
const prevDeps = prevState[1];
// 比较和现在的依赖项是否相同
if (areHookInputsEqual(nextDeps, prevDeps)) {
// 如果相同,则返回之前的值
return prevState[0];
}
}
}
// ...
// 否则重新计算
const nextValue = nextCreate();
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
```
首先,仍然是从 updateWorkInProgressHook 上面拿到之前的 hook 对象,从而获取到之前的依赖项目,然后和新传入的依赖项目就行一个对比,如果依赖项目没有变化,则返回之前的计算值,否则就执行传入的函数重新进行计算,最后向外部返回新的计算值。
## 真题解答
> 题目useCallback 和 useMemo 的区别是什么?
>
> 参考答案:
>
> 在 useCallback 内部,会将函数和依赖项一起缓存到 hook 对象的 memoizedState 属性上,在组件更新阶段,首先会拿到之前的 hook 对象,从之前的 hook 对象的 memoizedState 属性上获取到之前的依赖项目,对比依赖项目是否相同,如果相同返回之前的 callback否则就重新缓存然后返回新的 callback。
>
> 在 useMemo 内部,会将传入的函数执行并得到计算值,将计算值和依赖项目存储到 hook 对象的 memoizedState 上面,最后向外部返回计算得到的值。更新的时候首先是从 updateWorkInProgressHook 上拿到之前的 hook 对象,从而获取到之前的依赖值,和新传入的依赖进行一个对比,如果相同,就返回上一次的计算值,否则就重新调用传入的函数得到新的计算值并缓存,最后向外部返回新的计算值。