🤔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

相关文章
|
7月前
|
JavaScript 前端开发
useEffect与副作用
useEffect与副作用
63 0
|
缓存 前端开发 算法
【译】你真的应该使用useMemo吗? 让我们一起来看看
- 当处理量很大时,应该使用 useMemo - 从什么时候 useMemo 变得有用以避免额外处理,阈值在很大程度上取决于您的应用程序 - 数据在处理非常低的情况下使用 useMemo
2028 0
|
3月前
|
缓存 前端开发
React中函数式Hooks之memo、useCallback的使用以及useMemo、useCallback的区别
React中的`memo`是高阶组件,类似于类组件的`PureComponent`,用于避免不必要的渲染。`useCallback` Hook 用于缓存函数,避免在每次渲染时都创建新的函数实例。`memo`可以接收一个比较函数作为第二个参数,以确定是否需要重新渲染组件。`useMemo`用于缓存计算结果,避免重复计算。两者都可以用来优化性能,但适用场景不同:`memo`用于组件,`useMemo`和`useCallback`用于值和函数的缓存。
106 1
|
5月前
|
缓存 JavaScript 前端开发
useMemo问题之在什么情况下使用useMemo和useCallback是不必要的
useMemo问题之在什么情况下使用useMemo和useCallback是不必要的
|
5月前
|
缓存 前端开发
useMemo问题之提高组件第一次渲染的速度如何解决
useMemo问题之提高组件第一次渲染的速度如何解决
|
5月前
|
前端开发 JavaScript 数据格式
react18【系列实用教程】Hooks (useState,useReducer,useRef,useEffect,useContext,useMemo,useCallback,自定义 Hook )
react18【系列实用教程】Hooks (useState,useReducer,useRef,useEffect,useContext,useMemo,useCallback,自定义 Hook )
107 1
|
7月前
|
存储 缓存 前端开发
我知道你想用useEffect,但你先别急
useEffect是React提供给我们的一个“逃生舱”,是React 的纯函数式世界通往命令式世界的“逃生通道”,选择合适的时机使用useEffect会让我们的代码既优雅又高效,反之会造成不必要的负担。
|
存储 前端开发 JavaScript
useRef 和 useState 哪个更好?
useRef 和 useState 哪个更好?
112 1
|
缓存 前端开发
💡我居然用错了useMemo和useCallback这么久?
💡我居然用错了useMemo和useCallback这么久?
|
缓存 前端开发 开发者
useMemo 和 useCallback -React.memo
useMemo 和 useCallback -React.memo
66 0