hello,这里是潇晨,明天就带着大家一起来手写一个迷你版的hooks,不便大家了解hook在源码中的运行机制,配有图解,保姆级的教程,只求同学一个小小的,。

第一步:引入React和ReactDOM

因为咱们要将jsx转变为virtual-dom,这一步分工作就交给babel吧,而jsxbabel进行词法解析之后会造成React.createElement()的调用,而React.createElement()执行之后的返回后果就是jsx对象或者叫virtual-dom

又因为咱们要将咱们的demo渲染到dom上,所以咱们引入ReactDOM

import React from "react";import ReactDOM from "react-dom";

第二步:咱们来写一个小demo

咱们定义两个状态countage,在点击的时候触发更新,让它们的值加1。

在源码中useState是保留在一个Dispatcher对象下面的,并且在mountupdate的时候取到的是不同的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

在看这部分前,先来捋分明fiberhookupdate的关系,看图:

Dispatcher是什么:Dispatcher在源码中就是一个对象,下面寄存着各种各样的hooks,在mountupdate的时候会应用过不同的Dispatcher,来看看在源码中Dispatcher是什么样子:

在调用useState之后,会调用一个resolveDispatcher的函数,这个函数调用之后会返回一个dispatcher对象,这个对象上就有useState等钩子。

那咱们来看看这个函数做了啥事件,这个函数比较简单,间接从ReactCurrentDispatcher对象上拿到current,而后返回进去的这个current就是dispatcher,那这个ReactCurrentDispatcher又是个啥?别急,持续在源码中来找一下。

在源码中有这样一段代码,如果是在正式环境中,分为两种状况

  1. 如果满足 current === null || current.memoizedState === null,阐明咱们处于首次渲染的时候,也就是mount的时候,其中current就是咱们fiber节点,memoizedState保留了fiberhook,也就是说在利用首次渲染的时候,current fiber是不存在的,咱们还没有发明出任何fiber节点,或者存在某些fiber,然而下面没有构建相应的hook,这个时候就能够认为是处于首次渲染的时候,咱们取到的是HooksDispatcherOnMount
  2. 如果不满足 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;  }

那咱们就来看一下这个HooksDispatcherOnMountHooksDispatcherOnUpdate是个什么,好家伙,原来你蕴含了所有的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

当初咱们来手写一下dispatcherdispatcher是个对象,对象上存在useState,咱们用一个自执行函数来示意,此外还须要用到两个变量和一个常量fiber

  • workInProgressHook示意遍历到的hook(因为hook会保留在链表上,须要遍历链表计算hook上保留的状态)
  • 为了简略起见,定义一个isMount=true示意mount的时候,在update的时候将它设置成false
  • 为简略起见,fiber就定义成一个对象,memoizedState示意这个fiber节点上寄存的hook链表,stateNode就是第二步的demo。

相干参考视频解说:进入学习

let workInProgressHook;//当前工作中的hooklet isMount = true;//是否时mount时const fiber = {//fiber节点  memoizedState: null,//hook链表  stateNode: App};const Dispatcher = (() => {//Dispatcher对象  function useState(){    //。。。  }  return {    useState  };})();

在定义useState之前,首先来看看hookupdate的数据结构

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

    1. mount的时候:调用mountWorkInProgressHook创立一个初始的hook,赋值useState传进来的初始值initialState
    2. 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的返回  }

接下来定义mountWorkInProgressHookupdateWorkInProgressHook这两个函数

  • mountWorkInProgressHook:在mount的时候调用,新创建一个hook对象,

    1. 如果以后fiber不存在memoizedState,那以后hook就是这个fiber上的第一个hook,将hook赋值给fiber.memoizedState
    2. 如果以后fiber存在memoizedState,那将以后hook接在workInProgressHook.next前面。
    3. 将以后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

    1. 如果之前queue.pending不存在,那创立的这个update就是第一个,则update.next = update
    2. 如果之前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;//当前工作中的hooklet 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/cust...