🤔useMemo还可以这样用?useCallback:糟了,我成替身了!
文章首发公众号:萌萌哒草头将军,最近关注有🎁,欢迎关注!
最近在研究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,👉将注册的
callback和deps拿出来👉执行
callback,并将执行结果和deps缓存在当前hook的状态上👉
deps发生改变,进入update阶段,调用updateMemo👉取出当前的
hook状态,拿到callback和deps,再从当前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,👉将注册的
callback和deps拿出来👉将
callback和deps缓存在当前hook的状态上👉
deps发生改变,进入update阶段,调用updateCallback👉取出当前的
hook状态,拿到callback和deps,再从当前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;
}
从源码上看,useCallback和useMemo的实现十分类似,唯一的不同之处是:useMemo在依赖项发生变化时会缓存回调函数的返回值。
💎 总结
useCallback和useMemo都是缓存中间状态,
不同的是useMemo可以缓存任何类型的值,useCallback仅仅缓存函数。所以开头的例子可以按预期运行。
好了,今天的分享比较简单,但是希望可以帮你理解地更深一点。
下篇我们继续聊hook。
