hello,这里是潇晨,今天就带着大家一起来手写一个迷你版的hooks,方便大家理解hook在源码中的运行机制,配有图解,保姆级的教程,只求同学一个小小的👍,🐶。
第一步:引入React和ReactDOM
因为我们要将jsx转变为virtual-dom,这一步分工作就交给babel吧,而jsx被babel进行词法解析之后会形成React.createElement()的调用,而React.createElement()执行之后的返回结果就是jsx对象或者叫virtual-dom。
又因为我们要将我们的demo渲染到dom上,所以我们引入ReactDOM。
import React from "react";
import ReactDOM from "react-dom";
第二步:我们来写一个小demo
我们定义两个状态count和age,在点击的时候触发更新,让它们的值加1。
在源码中useState是保存在一个Dispatcher对象上面的,并且在mount和update的时候取到的是不同的hooks,所以我们先暂时从Dispatcher上拿到useState,等下在来定义Dispatcher。
接下来定义一个schedule函数,每次调用的时候会重新渲染组件。
function App() {
  let [count, setCount] = Dispatcher.useState(1);
  let [age, setAge] = Dispatcher.useState(10);
  return (
    <>
      <p>Clicked {count} times</p>
      <button onClick={() => setCount(() => count + 1)}> Add count</button>
      <p>Age is {age}</p>
      <button onClick={() => setAge(() => age + 1)}> Add age</button>
    </>
  );
}
function schedule() {    //每次调用会重新渲染组件
  ReactDOM.render(<App />, document.querySelector("#root"));
}
schedule();
第三步:定义Dispatcher
在看这部分前,先来捋清楚fiber、hook、update的关系,看图:

Dispatcher是什么:Dispatcher在源码中就是一个对象,上面存放着各种各样的hooks,在mount和update的时候会使用过不同的Dispatcher,来看看在源码中Dispatcher是什么样子:
在调用useState之后,会调用一个resolveDispatcher的函数,这个函数调用之后会返回一个dispatcher对象,这个对象上就有useState等钩子。

那我们来看看这个函数做了啥事情,这个函数比较简单,直接从ReactCurrentDispatcher对象上拿到current,然后返回出来的这个current就是dispatcher,那这个ReactCurrentDispatcher又是个啥?别急,继续在源码中来找一下。

在源码中有这样一段代码,如果是在正式环境中,分为两种情况
- 如果满足 current === null || current.memoizedState === null,说明我们处于首次渲染的时候,也就是mount的时候,其中current就是我们fiber节点,memoizedState保存了fiber上hook,也就是说在应用首次渲染的时候,current fiber是不存在的,我们还没有创造出任何fiber节点,或者存在某些fiber,但是上面没有构建相应的hook,这个时候就可以认为是处于首次渲染的时候,我们取到的是HooksDispatcherOnMount
- 如果不满足 current === null || current.memoizedState === null,就说明我们处于更新阶段,也就是update的时候,我们取到的是HooksDispatcherOnUpdate
if (__DEV__) {
    if (current !== null && current.memoizedState !== null) {
      ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV;
    } else if (hookTypesDev !== null) {
      ReactCurrentDispatcher.current = HooksDispatcherOnMountWithHookTypesInDEV;
    } else {
      ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV;
    }
  } else {
    ReactCurrentDispatcher.current =
      current === null || current.memoizedState === null
        ? HooksDispatcherOnMount
        : HooksDispatcherOnUpdate;
  }
那我们就来看一下这个HooksDispatcherOnMount和HooksDispatcherOnUpdate是个什么,好家伙,原来你包含了所有的hooks啊。
const HooksDispatcherOnMount: Dispatcher = {
  readContext,
  useCallback: mountCallback,
  useContext: readContext,
  useEffect: mountEffect,
  useImperativeHandle: mountImperativeHandle,
  useLayoutEffect: mountLayoutEffect,
  useMemo: mountMemo,
  useReducer: mountReducer,
  useRef: mountRef,
  useState: mountState,
  useDebugValue: mountDebugValue,
  useDeferredValue: mountDeferredValue,
  useTransition: mountTransition,
  useMutableSource: mountMutableSource,
  useOpaqueIdentifier: mountOpaqueIdentifier,
  unstable_isNewReconciler: enableNewReconciler,
};
const HooksDispatcherOnUpdate: Dispatcher = {
  readContext,
  useCallback: updateCallback,
  useContext: readContext,
  useEffect: updateEffect,
  useImperativeHandle: updateImperativeHandle,
  useLayoutEffect: updateLayoutEffect,
  useMemo: updateMemo,
  useReducer: updateReducer,
  useRef: updateRef,
  useState: updateState,
  useDebugValue: updateDebugValue,
  useDeferredValue: updateDeferredValue,
  useTransition: updateTransition,
  useMutableSource: updateMutableSource,
  useOpaqueIdentifier: updateOpaqueIdentifier,
  unstable_isNewReconciler: enableNewReconciler,
};
所以dispatcher就是个对象,里面包含了所有的hooks,在首次渲染和更新的时候拿到的是不同的dispatcher,在调用hooks的时候就会调用到不同的函数,比如如果使用了useState,在mount的时候调用到的就是mountState,在update的时候调用到的就是updateState。

现在我们来手写一下dispatcher,dispatcher是个对象,对象上存在useState,我们用一个自执行函数来表示,此外还需要用到两个变量和一个常量fiber
- workInProgressHook表示遍历到的- hook(因为- hook会保存在链表上,需要遍历链表计算- hook上保存的状态)
- 为了简单起见,定义一个isMount=true表示mount的时候,在update的时候将它设置成false,
- 为简单起见,fiber就定义成一个对象,memoizedState表示这个fiber节点上存放的hook链表,stateNode就是第二步的demo。
相关参考视频讲解:进入学习
let workInProgressHook;//当前工作中的hook
let isMount = true;//是否时mount时
const fiber = {//fiber节点
  memoizedState: null,//hook链表
  stateNode: App
};
const Dispatcher = (() => {//Dispatcher对象
  function useState(){
    //。。。
  }
  return {
    useState
  };
})();
在定义useState之前,首先来看看hook和update的数据结构
hook:
- queue:上面有- pending属性,- pending也是一条环状链表,上面存放了未被更新的- update,也就是说这些- update会以- next指针连接成环状链表。
- memoizedState表示当前的状态
- next:指向下一个- hook,形成一条链表
 const hook = {//构建hook
   queue: {
     pending: null//未执行的update链表
   },
   memoizedState: null,//当前state
   next: null//下一个hook
 };
update:
- action:是出发更新的函数
- next:连接下一个- update,形成一条环状链表
 const update = {//构建update
    action,
    next: null
  };
那接下来定义useState吧,分三个部分:
- 创建hook或取到hook:- 在mount的时候:调用mountWorkInProgressHook创建一个初始的hook,赋值useState传进来的初始值initialState
- 在update的时候:调用updateWorkInProgressHook,拿到当前正在工作的hook
 
- 在
- 计算hook上未更新的状态:遍历hook上的pending链表,调用链表节点上的action函数,生成一个新的状态,然后更新hook上的状态。
- 返回新的状态和dispatchAction传入queue参数
function useState(initialState) {
      //第1步:创建hook或取到hook
    let hook;
    if (isMount) {
      hook = mountWorkInProgressHook();
      hook.memoizedState = initialState;//初始状态
    } else {
      hook = updateWorkInProgressHook();
    }
        //第2步:计算hook上未更新的状态
    let baseState = hook.memoizedState;//初始状态
    if (hook.queue.pending) {
      let firstUpdate = hook.queue.pending.next;//第一个update
      do {
        const action = firstUpdate.action;
        baseState = action(baseState);//调用action计算新的状态
        firstUpdate = firstUpdate.next;//通过update的action计算state
      } while (firstUpdate !== hook.queue.pending);//当链表还没遍历完时 进行循环
      hook.queue.pending = null;//重置update链表
    }
    hook.memoizedState = baseState;//赋值新的state
        //第3步:返回新的状态和dispatchAction传入queue参数
    return [baseState, dispatchAction.bind(null, hook.queue)];//useState的返回
  }
接下来定义mountWorkInProgressHook和updateWorkInProgressHook这两个函数
- mountWorkInProgressHook:在- mount的时候调用,新创建一个- hook对象,- 如果当前fiber不存在memoizedState,那当前hook就是这个fiber上的第一个hook,将hook赋值给fiber.memoizedState
- 如果当前fiber存在memoizedState,那将当前hook接在workInProgressHook.next后面。
- 将当前hook赋值给workInProgressHook
 
- 如果当前
- updateWorkInProgressHook:在- update的时候调用,返回当前的- hook,也就是- workInProgressHook,并且将- workInProgressHook指向- hook链表的下一个。
function mountWorkInProgressHook() {//mount时调用
    const hook = {//构建hook
      queue: {
        pending: null//未执行的update链表
      },
      memoizedState: null,//当前state
      next: null//下一个hook
    };
    if (!fiber.memoizedState) {
      fiber.memoizedState = hook;//第一个hook的话直接赋值给fiber.memoizedState
    } else {
      workInProgressHook.next = hook;//不是第一个的话就加在上一个hook的后面,形成链表
    }
    workInProgressHook = hook;//记录当前工作的hook
    return workInProgressHook;
  }
function updateWorkInProgressHook() {//update时调用
  let curHook = workInProgressHook;
  workInProgressHook = workInProgressHook.next;//下一个hook
  return curHook;
}
第四步:定义dispatchAction
-  创建 update,挂载载queue.pending上- 如果之前queue.pending不存在,那创建的这个update就是第一个,则update.next = update
- 如果之前queue.pending存在,则将创建的这个update加入queue.pending的环状链表中
  
- 如果之前
-  让 isMount=false,并且赋值workInProgressHook,调用schedule进行更新渲染
function dispatchAction(queue, action) {//触发更新
  const update = {//构建update
    action,
    next: null
  };
  if (queue.pending === null) {
    update.next = update;//update的环状链表
  } else {
    update.next = queue.pending.next;//新的update的next指向前一个update
    queue.pending.next = update;//前一个update的next指向新的update
  }
  queue.pending = update;//更新queue.pending
  isMount = false;//标志mount结束
  workInProgressHook = fiber.memoizedState;//更新workInProgressHook
  schedule();//调度更新
}
最终代码
import React from "react";
import ReactDOM from "react-dom";
let workInProgressHook;//当前工作中的hook
let isMount = true;//是否时mount时
const fiber = {//fiber节点
  memoizedState: null,//hook链表
  stateNode: App//dom
};
const Dispatcher = (() => {//Dispatcher对象
  function mountWorkInProgressHook() {//mount时调用
    const hook = {//构建hook
      queue: {
        pending: null//未执行的update链表
      },
      memoizedState: null,//当前state
      next: null//下一个hook
    };
    if (!fiber.memoizedState) {
      fiber.memoizedState = hook;//第一个hook的话直接赋值给fiber.memoizedState
    } else {
      workInProgressHook.next = hook;//不是第一个的话就加在上一个hook的后面,形成链表
    }
    workInProgressHook = hook;//记录当前工作的hook
    return workInProgressHook;
  }
  function updateWorkInProgressHook() {//update时调用
    let curHook = workInProgressHook;
    workInProgressHook = workInProgressHook.next;//下一个hook
    return curHook;
  }
  function useState(initialState) {
    let hook;
    if (isMount) {
      hook = mountWorkInProgressHook();
      hook.memoizedState = initialState;//初始状态
    } else {
      hook = updateWorkInProgressHook();
    }
    let baseState = hook.memoizedState;//初始状态
    if (hook.queue.pending) {
      let firstUpdate = hook.queue.pending.next;//第一个update
      do {
        const action = firstUpdate.action;
        baseState = action(baseState);
        firstUpdate = firstUpdate.next;//循环update链表
      } while (firstUpdate !== hook.queue.pending);//通过update的action计算state
      hook.queue.pending = null;//重置update链表
    }
    hook.memoizedState = baseState;//赋值新的state
    return [baseState, dispatchAction.bind(null, hook.queue)];//useState的返回
  }
  return {
    useState
  };
})();
function dispatchAction(queue, action) {//触发更新
  const update = {//构建update
    action,
    next: null
  };
  if (queue.pending === null) {
    update.next = update;//update的环状链表
  } else {
    update.next = queue.pending.next;//新的update的next指向前一个update
    queue.pending.next = update;//前一个update的next指向新的update
  }
  queue.pending = update;//更新queue.pending
  isMount = false;//标志mount结束
  workInProgressHook = fiber.memoizedState;//更新workInProgressHook
  schedule();//调度更新
}
function App() {
  let [count, setCount] = Dispatcher.useState(1);
  let [age, setAge] = Dispatcher.useState(10);
  return (
    <>
      <p>Clicked {count} times</p>
      <button onClick={() => setCount(() => count + 1)}> Add count</button>
      <p>Age is {age}</p>
      <button onClick={() => setAge(() => age + 1)}> Add age</button>
    </>
  );
}
function schedule() {
  ReactDOM.render(<App />, document.querySelector("#root"));
}
schedule();
预览效果:https://codesandbox.io/s/custom-hook-tyf19?file=/src/index.js



















