React useState 极简实现

243 阅读1分钟

React useState 极简实现

useState 就是一个预置了 useReduceruseReducer

在内存中始终会有一个 fiber 对象,采用链表的结构来保存, 每个useState 都会产生一个 fiber 节点,当我们更新状态时,参与运算的是 workInProgressHook , 它指向 fiber 节点的第一个hook,

//  update
     hook = workInProgressHook;
     workInProgressHook = workInProgressHook.next;

通过上述代码,依次去执行对应fiber更新,

循环链表的好处

  1. 任何节点都可以做为头节点。 可以从任何节点开始进行链表的遍历。只要当第一个节点被重复访问时,则意味着遍历结束。

  2. 用于实现队列数据结构是很有帮组的。 如果使用循环链表,则不需要为了队列而维护两个指针(front以及rear)。只需要维护尾节点一个指针即可,因为尾节点的后向节点就是front了。

  3. 循环链表常用于各应用程序中。 例如,当运行多个应用程序时,操作系统通常会把这些程序存入至一个链表,并进行循环遍历,给每个应用程序分配一定的时间来执行。此时循环链表对于OS是很有帮助的,当达到链表尾部时,可以方便的从头部重新开始遍历。

  4. 循环双向链表可以用于实现高级数据结构,例如斐波那契堆(Fibonacci Heap)。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body></body>
  <script>
    /**
     *  react中 我们可以通过current.fiber是否存在,来判断是mount 或 update
     *  这里,我们用一个全局变量来进行判断 isMount
     */
    let isMount = true;
    let workInProgressHook = null; // 使用链表来保存 hooks, 参与计算的hook
​
    const fiber = {
      memoizedState: null, // 收集hook,组成链表
      stateNode: App, // 保存当前函数组件
    };
​
    function dispatchAction(queue, action) {
      const update = {
        action,
        next: null,
      };
      if (queue.pending === null) {
        update.next = update;
      } else {
        update.next = queue.pending.next; // 当前的指向第一个
        queue.pending.next = update;
      }
      // 指向最后一个action
      queue.pending = update;
      run();
    }
​
    function useState(initialState) {
      let hook;
      // 区分mount 和 update
      if (isMount) {
        // 创建hooks数据结构
        hook = {
          queue: {
            pending: null,
          },
          // 保存了hooks对应的state
          memoizedState: initialState,
          // 指向下一个hook
          next: null,
        };
        // mount时, 将hook 挂到 workInProgressHook 后面
        if (!fiber.memoizedState) {
          fiber.memoizedState = hook; // fiber -> hook 第一次
        } else {
          workInProgressHook.next = hook; // 1. workInPh.next -> hook 2. hook.next -> hook
        }
        workInProgressHook = hook; //
      } else {
        //  update
        hook = workInProgressHook;
        workInProgressHook = workInProgressHook.next;
      }
​
      let baseState = hook.memoizedState;
      //   此处进行更新操作, 基于baseState 进行更新
      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.next);
        hook.queue.pending = null;
      }
      //   将计算完成的baseState 保存到memoizedState, 下次更新将以memoizedState为基础
      hook.memoizedState = baseState;
      //    传递 hook.queue, 就能在dispatchAction 取到pending
      return [baseState, dispatchAction.bind(null, hook.queue)];
    }
​
    function App() {
      const [num, setNum] = useState(1);
      const [count, setCount] = useState(2);
      // 调用useState方法,会执行state的计算过程,返回的updater方法 会执行updater的创建过程
      console.log(num, count);
      return {
        onClick: () => {
          setNum((num) => num + 1);
          setCount((count) => count + 1);
        },
        setCount: () => {
          setCount((count) => count + 1);
        },
      };
    }
​
    //  模拟 scheduker render commit 流程
    function run() {
      // hook的初始化操作,将fiber节点的第一个hook 赋值给参与计算的 workInProgressHook
      workInProgressHook = fiber.memoizedState;
      const app = fiber.stateNode(); // render阶段,执行函数组件
      isMount = false;
      return app;
    }
​
    window.app = run();
  </script>
</html>
​

\