zoukankan      html  css  js  c++  java
  • 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值。


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

  • 相关阅读:
    一个简单的knockout.js 和easyui的绑定
    knockoutjs + easyui.treegrid 可编辑的自定义绑定插件
    Knockout自定义绑定my97datepicker
    去除小数后多余的0
    Windows Azure Web Site (15) 取消Azure Web Site默认的IIS ARR
    Azure ARM (1) UI初探
    Azure Redis Cache (3) 创建和使用P级别的Redis Cache
    Windows Azure HandBook (7) 基于Azure Web App的企业官网改造
    Windows Azure Storage (23) 计算Azure VHD实际使用容量
    Windows Azure Virtual Network (11) 创建VNet-to-VNet的连接
  • 原文地址:https://www.cnblogs.com/cczlovexw/p/14336219.html
Copyright © 2011-2022 走看看