乐趣区

关于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…

退出移动版