关于react.js:来来来手摸手写一个hook

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;//当前工作中的hook
let 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;//当前工作中的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/cust…

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理