likes
comments
collection
share

react hooks 本质探索 - useCallback源码解析

作者站长头像
站长
· 阅读数 21

这次来看useCallback。useCallback是所有原生use中比较简单的函数

其实整体跟useRef/useState类似。

我们直接看源码。

初始化的时候,主要执行如下代码:

function mountCallback(callback, deps) {
  // 获取当前钩子,也就是链表指针
  var hook = mountWorkInProgressHook();
  
  // deps是useCallback的第二个参数,也就是什么情况下需要更新callback
  var nextDeps = deps === undefined ? null : deps;
  
  // 两个值存到hook.memoizedState,也是hooks这套代码的常见手段
  hook.memoizedState = [callback, nextDeps];
  
  // 返回callback。就是传入什么,返回什么。
  return callback;
}

关键是第二次渲染functional component时做的事情:

function updateCallback(callback, deps) {
  // 通用做法。  
  var hook = updateWorkInProgressHook();
  
  // 获取当前的依赖值,是一个数组
  var nextDeps = deps === undefined ? null : deps;
  
  // 获取之前的依赖值,用于对比的。也是一个数组,这个数组中[1]的值才是依赖值
  var prevState = hook.memoizedState;

  // 检测确定prevState存在。什么情况不存在?暂时不知道
  if (prevState !== null) {
    //  检测nextDeps存在。这一行和上一行的判断是可以合并的。
    if (nextDeps !== null) {
      // 前面说道,prevState上[1]的值才是之前的依赖值。
      var prevDeps = prevState[1];
      
      // 判断当前的依赖的值和之前依赖的值是否相等
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        // prevState[0]就是之前的那个callback,说明函数没变,还是那个  
        return prevState[0];
      }
    }
  }

  // 这里是prevState不存在,或者nextDeps(当前值)和prevDeps(前值)不同
  // 或者nextDeps不存在(等于是变化了,也就是说,如果第二个参数传入null,每次都会重新定义函数)
  hook.memoizedState = [callback, nextDeps];
  // 返回新函数
  return callback;
}

附上areHookInputsEqual的源码:

function areHookInputsEqual(nextDeps, prevDeps) {
  {
    if (ignorePreviousDependencies) {
      // Only true when this component is being hot reloaded.
      return false;
    }
  }

  if (prevDeps === null) {
    {
      error('%s received a final argument during this render, but not during ' + 'the previous render. Even though the final argument is optional, ' + 'its type cannot change between renders.', currentHookNameInDev);
    }

    return false;
  }

  {
    // Don't bother comparing lengths in prod because these arrays should be
    // passed inline.
    if (nextDeps.length !== prevDeps.length) {
      error('The final argument passed to %s changed size between renders. The ' + 'order and size of this array must remain constant.\n\n' + 'Previous: %s\n' + 'Incoming: %s', currentHookNameInDev, "[" + prevDeps.join(', ') + "]", "[" + nextDeps.join(', ') + "]");
    }
  }

  // 只有这部分是重要的,这里是做了浅比较。
  for (var i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
    if (objectIs(nextDeps[i], prevDeps[i])) {
      continue;
    }

    return false;
  }

  return true;
}