• useState原理解析


    一、初始化

    构建dispatcher函数和初始值

    二、更新时

    1. 调用dispatcher函数,按序插入update(其实就是一个action)

    2. 收集update,调度一次React的更新

    3. 在更新的过程中将ReactCurrentDispatcher.current指向负责更新的Dispatcher

    4. 执行到函数组件App()时,useState会被重新执行,在resolve dispatcher的阶段拿到了负责更新的dispatcher。

    5. useState会拿到Hook对象,Hook.query中存储了更新队列,依次进行更新后,即可拿到最新的state

    6. 函数组件App()执行后返回的nextChild中的count值已经是最新的了。FiberNode中的memorizedState也被设置为最新的state

    7. Fiber渲染出真实DOM。更新结束

    三、 了解useState

    useState的引入

    // React.js
    import {
    useCallback,
    useContext,
    useEffect,
    useImperativeHandle,
    useDebugValue,
    useLayoutEffect,
    useMemo,
    useReducer,
    useRef,
    useState,
    } from './ReactHooks';

    所有的Hooks在React.js中被引入,挂载在React对象中

    useState的实现

    // ReactHooks.js
    export function useState<S>(initialState: (() => S) | S) {
      const dispatcher = resolveDispatcher();
      return dispatcher.useState(initialState);
    }

    重点都在这个dispatcher上,dispatcher通过resolveDispatcher()来获取,这个函数同样也很简单,只是将ReactCurrentDispatcher.current的值赋给了dispatcher。

    // ReactHooks.js
    function resolveDispatcher() {
      const dispatcher = ReactCurrentDispatcher.current;
      return dispatcher;
    }

    ReactCurrentDispatcher.current.useStateuseState能够触发更新的关键原因,这个方法的实现并不在react包内。

    四. 核心步骤分析

    ReactFiberHooks.js包含着各种关于Hooks逻辑的处理

    Hook对象的结构如下:

    // ReactFiberHooks.js
    export type Hook = {
      memoizedState: any, 
     
      baseState: any,    
      baseUpdate: Update<any, any> | null,  
      queue: UpdateQueue<any, any> | null,  
     
      next: Hook | null, 
    };

    在类组件中state是一整个对象,可以和memoizedState一一对应。但是在Hooks中,React并不知道我们调用了几次useState,所以React通过将一个Hook对象挂载在memorizedStated上来保存函数组件的state

    重点关注memoizedStatenext

    • memoizedState是用来记录当前useState应该返回的结果的

    • query:缓存队列,存储多次更新行为

    • next:指向下一次useState对应的Hook对象。

    renderWithHooks

    renderWithHooks的运行过程如下:

    // ReactFiberHooks.js
    export function renderWithHooks(
      current: Fiber | null,
      workInProgress: Fiber,
      Component: any,
      props: any,
      refOrContext: any,
      nextRenderExpirationTime: ExpirationTime,
    ): any {
      renderExpirationTime = nextRenderExpirationTime;
      currentlyRenderingFiber = workInProgress;
     
      // 如果current的值为空,说明还没有hook对象被挂载
      // 而根据hook对象结构可知,current.memoizedState指向下一个current
      nextCurrentHook = current !== null ? current.memoizedState : null;
     
      // 用nextCurrentHook的值来区分mount和update,设置不同的dispatcher
      ReactCurrentDispatcher.current =
          nextCurrentHook === null
          // 初始化时
            ? HooksDispatcherOnMount
              // 更新时
            : HooksDispatcherOnUpdate;
     
      // 此时已经有了新的dispatcher,在调用Component时就可以拿到新的对象
      let children = Component(props, refOrContext);
     
      // 重置
      ReactCurrentDispatcher.current = ContextOnlyDispatcher;
     
      const renderedWork: Fiber = (currentlyRenderingFiber: any);
     
      // 更新memoizedState和updateQueue
      renderedWork.memoizedState = firstWorkInProgressHook;
      renderedWork.updateQueue = (componentUpdateQueue: any);
     
       /** 省略与本文无关的部分代码,便于理解 **/
    }

    初始化时

    核心:创建一个新的hook,初始化state, 并绑定触发器

     初始化阶段ReactCurrentDispatcher.current 会指向HooksDispatcherOnMount 对象

    // ReactFiberHooks.js
     
    const HooksDispatcherOnMount: Dispatcher = {
    /** 省略其它Hooks **/
      useState: mountState,
    };
     
    // 所以调用useState(0)返回的就是HooksDispatcherOnMount.useState(0),也就是mountState(0)
    function mountState<S>(
      initialState: (() => S) | S,
    ): [S, Dispatch<BasicStateAction<S>>] {
        // 访问Hook链表的下一个节点,获取到新的Hook对象
      const hook = mountWorkInProgressHook();
    //如果入参是function则会调用,但是不提供参数
      if (typeof initialState === 'function') {
        initialState = initialState();
      }
    // 进行state的初始化工作
      hook.memoizedState = hook.baseState = initialState;
    // 进行queue的初始化工作
      const queue = (hook.queue = {
        last: null,
        dispatch: null,
        eagerReducer: basicStateReducer, // useState使用基础reducer
        eagerState: (initialState: any),
      });
        // 返回触发器
      const dispatch: Dispatch<BasicStateAction<S>,> 
        = (queue.dispatch = (dispatchAction.bind(
            null,
            //绑定当前fiber结点和queue
            ((currentlyRenderingFiber: any): Fiber),
            queue,
      ));
      // 返回初始state和触发器
      return [hook.memoizedState, dispatch];
    }
     
    // 对于useState触发的update action来说(假设useState里面都传的变量),basicStateReducer就是直接返回action的值
    function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
      return typeof action === 'function' ? action(state) : action;
    }

    更新函数 dispatchAction

    function dispatchAction<S, A>(
      fiber: Fiber,
      queue: UpdateQueue<S, A>,
      action: A,
    ) {
     
       /** 省略Fiber调度相关代码 **/
     
      // 创建新的新的update, action就是我们setCount里面的值(count+1, count+2, count+3…)
        const update: Update<S, A> = {
          expirationTime,
          action,
          eagerReducer: null,
          eagerState: null,
          next: null,
        };
     
        // 重点:构建query
        // queue.last是最近的一次更新,然后last.next开始是每一次的action
        const last = queue.last;
        if (last === null) {
          // 只有一个update, 自己指自己-形成环
          update.next = update;
        } else {
          const first = last.next;
          if (first !== null) {
     
            update.next = first;
          }
          last.next = update;
        }
        queue.last = update;
     
        /** 省略特殊情况相关代码 **/
     
        // 创建一个更新任务
        scheduleWork(fiber, expirationTime);
     
    }

    dispatchAction中维护了一份query的数据结构。

    query是一个有环链表,规则:

    • query.last指向最近一次更新

    • last.next指向第一次更新

    • 后面就依次类推,最终倒数第二次更新指向last,形成一个环。

    所以每次插入新update时,就需要将原来的first指向query.last.next。再将update指向query.next,最后将query.last指向update.

    更新时

    核心:获取该Hook对象中的 queue,内部存有本次更新的一系列数据,进行更新

    更新阶段 ReactCurrentDispatcher.current 会指向HooksDispatcherOnUpdate对象

    // ReactFiberHooks.js
     
    // 所以调用useState(0)返回的就是HooksDispatcherOnUpdate.useState(0),也就是updateReducer(basicStateReducer, 0)
     
    const HooksDispatcherOnUpdate: Dispatcher = {
      /** 省略其它Hooks **/
       useState: updateState,
    }
     
    function updateState(initialState) {
      return updateReducer(basicStateReducer, initialState);
    }
     
    // 可以看到updateReducer的过程与传的initalState已经无关了,所以初始值只在第一次被使用
     
    // 为了方便阅读,删去了一些无关代码
    // 查看完整代码:https://github.com/facebook/react/blob/487f4bf2ee7c86176637544c5473328f96ca0ba2/packages/react-reconciler/src/ReactFiberHooks.js#L606
    function updateReducer(reducer, initialArg, init) {
    // 获取初始化时的 hook
      const hook = updateWorkInProgressHook();
      const queue = hook.queue;
     
      // 开始渲染更新
      if (numberOfReRenders > 0) {
        const dispatch = queue.dispatch;
        if (renderPhaseUpdates !== null) {
          // 获取Hook对象上的 queue,内部存有本次更新的一系列数据
          const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
          if (firstRenderPhaseUpdate !== undefined) {
            renderPhaseUpdates.delete(queue);
            let newState = hook.memoizedState;
            let update = firstRenderPhaseUpdate;
            // 获取更新后的state
            do {
              const action = update.action;
              // 此时的reducer是basicStateReducer,直接返回action的值
              newState = reducer(newState, action);
              update = update.next;
            } while (update !== null);
            // 对 更新hook.memoized 
            hook.memoizedState = newState;
            // 返回新的 state,及更新 hook 的 dispatch 方法
            return [newState, dispatch];
          }
        }
      }
     
    // 对于useState触发的update action来说(假设useState里面都传的变量),basicStateReducer就是直接返回action的值
    function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
      return typeof action === 'function' ? action(state) : action;
    }

    总结

    单个hooks的更新行为全都挂在Hooks.queue下,所以能够管理好queue的核心就在于

    • 初始化queue - mountState

    • 维护queue - dispatchAction

    • 更新queue - updateReducer

    结合示例代码:

    • 当我们第一次调用[count, setCount] = useState(0)时,创建一个queue

    • 每一次调用setCount(x),就dispach一个内容为x的action(action的表现为:将count设为x),action存储在queue中,以前面讲述的有环链表规则来维护

    • 这些action最终在updateReducer中被调用,更新到memorizedState上,使我们能够获取到最新的state值。


    文章就分享到这,欢迎关注“前端大神之路

  • 相关阅读:
    [HDU] 1016 Prime Ring Problem(DFS)
    ACM、OI等比赛中的程序对拍问题
    [POJ] 1948 Triangular Pastures (DP)
    [POJ] 1606 Jugs(BFS+路径输出)
    [百度2015春季实习生招聘附加题] 今天要吃点好的!
    Idea 搭建Maven--web项目(MVC)
    Python自动化测试框架——数据驱动(从文件中读取)
    Python自动化测试框架——数据驱动(从代码中读取)
    selenium——操作滚动条
    Python自动化测试框架——生成测试报告
  • 原文地址:https://www.cnblogs.com/cczlovexw/p/14336219.html
Copyright © 2020-2023  润新知