第二章: 关于Hook的一些前置知识

167 阅读2分钟

Hook的一些基本知识

首先在遍历函数时会将hook相关的变量赋值,赋值给ReactCurrentDispatcher.current对象,然后调用函数组建,这样函数内调用Hook的就会使用对应的方法,在函数组建执行之后,就将之前的Hook对象重新赋值给ReactCurrentDispatcher.current,这样函数外调用Hook就会报错。初始化的Hook函数就是抛出一些错误,控制台内会打印出异常。

在不同阶段中,会将ReactCurrentDispatcher.current赋值给不同的对象属性,即看起来使用的是同一个方法,但在代码底层中已经变更了核心方法,所以在不同阶段会有不同的效果。


hook的一些常规用法

绝大多数的hook都会创建hook对象,在mount阶段调用mountWorkInProgressHook方法,

// 创建一个全局hook对象
let workInProgressHook = null

// 以下是 mountWorkInProgressHook 的代码
const hook = {
    memoizedState: null,
    baseState: null,
    baseQueue: null,
    queue: null,
    next: null,
};
// 是否是当前函数的第一个hook
if(workInProgressHook !== null){
    fiber.memoizedState = workInProgressHook = hook
}else{
// 后续创建的hook都放在上一个hook.next对象
    workInProgressHook = workInProgressHook.next = hook
}
return workInProgressHook;

// 当执行完这个函数组件之后
workInProgressHook = null;

与此对应的是update阶段创建hook对象,调用updateWorkInProgressHook方法,

创建一个全局属性
let currentHook = null

// 以下是updateWorkInProgressHook
let nextCurrentHook;
if(currentHook === null){
    const current = fiber.alternate;
    if(current !== null){
        // 当前fiber 第一个hook,获取另外一条fiber树上的hook链表
        nextCurrentHook = current.memoizedState;
    } else {
        // 异常状态
        nextCurrentHook = null;
    }
} else {
    // 获取链表中下一个hook对象,第二次及其以后的hook
    nextCurrentHook = currentHook.next;
}

currentHook = nextCurrentHook;
// 浅copy hook对象
const newHook = {
  memoizedState: currentHook.memoizedState,
  baseState: currentHook.baseState,
  baseQueue: currentHook.baseQueue,
  queue: currentHook.queue,
  next: null
};
// 重新更新hook链表对象
if (workInProgressHook === null) {
  fiber.memoizedState = workInProgressHook = newHook;
} else {
  workInProgressHook = workInProgressHook.next = newHook;
}

return workInProgressHook;

// 当执行完这个函数组件之后
currentHook = null;

updateWorkInProgressHook 是源码简化之后的,有一些逻辑处理不会在常规下使用到的兼容,所以就直接省略。
上面说绝大多数的hook都会创建hook对象,为什么说绝大多数,比如useContext就会创建hook对象,所以上面的代码就不适用。

hook的检查逻辑

每个hook调用都会有hook检查机制,为了避免出现hook在if或者循环语句中。

// 全局变量中
let currentHookNameInDev = null;
let hookTypesDev = null;
let hookTypesUpdateIndexDev = -1;
// mount阶段调用 mountHookTypesDev 方法
const hookName = currentHookNameInDev;
if (hookTypesDev === null) {
  hookTypesDev = [hookName];
} else {
  hookTypesDev.push(hookName);
}

// update阶段调用updateHookTypesDev方法检查
const hookName = currentHookNameInDev;
if (hookTypesDev !== null) {
  hookTypesUpdateIndexDev++;
  if (hookTypesDev[hookTypesUpdateIndexDev] !== hookName) {
    // console.error 方法报错
    warnOnHookMismatchInDev(hookName);
  }
}

hook的检查机制就是一个hook数组,对比前后两次hook数组的hookName是否相同。
currentHookNameInDev是useXxx的name。

// todo pushEffect 和生命周期相关的创建