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值。


useState原理解析文章就分享到这,欢迎关注“前端大神之路useState原理解析

useState原理解析

 

上一篇:react hook的学习(该篇只是为了下一篇关于jest对react hook的测试的基础)


下一篇:简单实现React的useState钩子