🤔useMemo还可以这样用?useCallback:糟了,我成替身了!

简介: 🤔useMemo还可以这样用?useCallback:糟了,我成替身了!

最近在研究React的源码,然后,我就悟了!

💡推荐阅读

🎉干货满满,React设计原理,藏在源码里的五指山🎉

💎 开门见山

请看👇的代码:你觉得可以按预期运行吗?

import { useMemo, useState } from 'react'
function App() {
  const [count, setCount] = useState(0)
  const onClick = useMemo(() => {
      return () => setCount((count) => count + 1)
  }, [])
  useMemo(() => console.log(count), [count])
  return (
    <div className="App">
      <button onClick={onClick}>
          count is {count}
        </button>
    </div>
  );
}
export default App;

答案是完全可以!

💎 分析

🚗 用法分析

他们都接收两个参数,useXxx(callback, [...deps])

  • 👉第一个参数callback是回调函数
  • 👉第二个参数deps是依赖项

不同的是当依赖项发生改变时

  • 🚆useCallback会重新创建回调函数,以保证每次调用都是最新值。并缓存这个函数
  • 🚆useEffect回调函数会重新执行
  • 🚆useMemo回调函数会重新执行,并缓存返回值。

根据useMemo返回值的不同,可以模拟出不同的效果:

  • 👉当返回值是个函数时,它useCallback和是完全等效的。
  • 👉当没有返回值或者不管返回值时,它useEffect和部分功能是等效的

这是因为,它不会像useEffect一样,对返回值做处理。也就是说,它无法模拟unMounted生命周期函数。

就是这么简单的原因,上面的代码会执行成功。

🚗 源码分析

这部分是选读,如果你对源码感兴趣,可以阅读这块。

🚆 useMemo源码逻辑

  • 👉注册hook状态
  • 👉此时是mounted阶段,调用mountMemo
  • 👉将注册的callbackdeps拿出来
  • 👉执行callback,并将执行结果和deps缓存在当前hook的状态上
  • 👉deps发生改变,进入update阶段,调用updateMemo
  • 👉取出当前的hook状态,拿到callbackdeps,再从当前hook拿到上次的deps
  • 👉比较前后两次的deps,如果一致,直接返回当前的状态值
  • 👉否则重新执行callback,保持返回值,并将该值最为最新的状态值和deps一起保存起来
function mountMemo<T>(
  nextCreate: () => T,
  deps: Array<mixed> | void | null,
): T {
  // 拿到当前的hook状态
  const hook = mountWorkInProgressHook();
  // 拿到当前的hook依赖项
  const nextDeps = deps === undefined ? null : deps;
  if (shouldDoubleInvokeUserFnsInHooksDEV) {
    nextCreate();
  }
  // 执行回调函数
  const nextValue = nextCreate();
  // 缓存回调函数返回值和依赖
  hook.memoizedState = [nextValue, nextDeps];
  // 返回返回值
  return nextValue;
}
function updateMemo<T>(
  nextCreate: () => T,
  deps: Array<mixed> | void | null,
): T {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;
  // Assume these are defined. If they're not, areHookInputsEqual will warn.
  if (nextDeps !== null) {
    const prevDeps: Array<mixed> | null = prevState[1];
    if (areHookInputsEqual(nextDeps, prevDeps)) {
      // 如果前后依赖相同时,直接返回当前值
      return prevState[0];
    }
  }
  if (shouldDoubleInvokeUserFnsInHooksDEV) {
    nextCreate();
  }
  // 否则重新计算赋值,并返回最新值
  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}

🚆 useCallback源码逻辑

  • 👉注册hook状态
  • 👉此时是mounted阶段,调用mountCallback
  • 👉将注册的callbackdeps拿出来
  • 👉将callbackdeps缓存在当前hook的状态上
  • 👉deps发生改变,进入update阶段,调用updateCallback
  • 👉取出当前的hook状态,拿到callbackdeps,再从当前hook拿到上次的deps
  • 👉比较前后两次的deps,如果一致,直接返回当前的状态值
  • 👉否则重新将callback做为最新的状态值和deps一起保存起来
function mountCallback<T>(
  callback: T,
  deps: Array<mixed> | void | null
): T {
  // 获取当前hook状态
  const hook = mountWorkInProgressHook();
  // 获取当前hook依赖项
  const nextDeps = deps === undefined ? null : deps;
  // 缓存回调函数和依赖
  hook.memoizedState = [callback, nextDeps];
  // 返回回调函数
  return callback;
}
function updateCallback<T>(
  callback: T,
  deps: Array<mixed> | void | null
): T {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;
  if (nextDeps !== null) {
    const prevDeps: Array<mixed> | null = prevState[1];
    if (areHookInputsEqual(nextDeps, prevDeps)) {
      // 如果依赖项相同时,直接返回当前值
      return prevState[0];
    }
  }
  // 否则重新赋值,并返回最新值
  hook.memoizedState = [callback, nextDeps];
  return callback;
}

从源码上看,useCallbackuseMemo的实现十分类似,唯一的不同之处是:useMemo在依赖项发生变化时会缓存回调函数的返回值。

💎 总结

useCallbackuseMemo都是缓存中间状态,

不同的是useMemo可以缓存任何类型的值,useCallback仅仅缓存函数。所以开头的例子可以按预期运行。

好了,今天的分享比较简单,但是希望可以帮你理解地更深一点。

下篇我们继续聊hook

相关文章
|
4月前
|
JavaScript 前端开发
useEffect与副作用
useEffect与副作用
28 0
|
4月前
|
缓存
UseMemo和useCallback如何提升了性能,应用场景。
UseMemo和useCallback如何提升了性能,应用场景。
|
6月前
|
前端开发 JavaScript 安全
useEffect 与 useLayoutEffect区别
useEffect 与 useLayoutEffect区别
35 0
|
缓存 前端开发 算法
【译】你真的应该使用useMemo吗? 让我们一起来看看
- 当处理量很大时,应该使用 useMemo - 从什么时候 useMemo 变得有用以避免额外处理,阈值在很大程度上取决于您的应用程序 - 数据在处理非常低的情况下使用 useMemo
1926 0
|
9月前
|
JavaScript
useEffect和useLayoutEffect有什么区别
useEffect和useLayoutEffect有什么区别
|
4月前
|
缓存 前端开发 API
useCallback 使用的4个阶段
useCallback 使用的4个阶段
|
5月前
|
前端开发 JavaScript
useState和useReducer的区别?
useState和useReducer的区别?
36 0
|
5月前
|
存储 前端开发 JavaScript
useRef 和 useState 哪个更好?
useRef 和 useState 哪个更好?
39 1
|
7月前
|
缓存 前端开发
💡我居然用错了useMemo和useCallback这么久?
💡我居然用错了useMemo和useCallback这么久?
|
6月前
|
缓存 前端开发 开发者
useMemo 和 useCallback -React.memo
useMemo 和 useCallback -React.memo
23 0