React源码学习(一):useState源码分析和实现

162 阅读4分钟
本文章分析基于react 18.2源码分析

hook 原理

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

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

ReactFiberHooks.old

/* 
  简单分析一下 Hook对象
  在函数组件之中,react并不知道我们调用了几次hook,所以react将对象挂载在fiber.memorizedStated上来保存函数组件的state
  memoizedState: 是用来记录当前useState、useReducer应该返回的结果的
  queue: 缓存队列,用来记录多次更新行为
  next:指向下一个hook的指针
*/
fiber .memorizedState(hook0)-> next(hook1)-> next(hook2)->next(hook3) (workInProgressHook)

export type Hook = {|
  memoizedState: any,
  baseState: any,
  baseQueue: Update<any, any> | null,
  queue: any,
  next: Hook | null,
|};

memoizedState的结构如下

image.png

useState

初始化时

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

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

const HooksDispatcherOnMount: Dispatcher = {
  readContext,
  /** 省略其它Hooks **/
  useState: mountState, // 函数组件useState挂载时调用
};

/* 
  useState 挂载时调用 mountState
*/
function mountState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  const hook = mountWorkInProgressHook();
  // initialState () => S 初始化为函数则优先调用函数,但是不提供参数
  if (typeof initialState === 'function') {
    // $FlowFixMe: Flow doesn't like mixed types
    initialState = initialState();
  }
  /* 
    进行state的初始化工作
    S 值则直接通过 memoizedState赋值
    hook.baseState 记录当前值 ----> 用于更新相同值时做视图渲染判断
  */
  hook.memoizedState = hook.baseState = initialState;
  /* 
    进行queue的初始化工作
    因为 hook作为单链表存在于fiber,所以在function组件中不能将hook置于条件判断中
  */
  const queue: UpdateQueue<S, BasicStateAction<S>> = {
    pending: null,
    interleaved: null,
    lanes: NoLanes,
    dispatch: null,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: (initialState: any),
  };
  hook.queue = queue;
  /* 
    currentlyRenderingFiber 用于获取当前 fiber
    dispatch 返回触发器
  */
  const dispatch: Dispatch<
    BasicStateAction<S>,
  > = (queue.dispatch = (dispatchSetState.bind(
    null,
    //绑定当前fiber结点和queue
    currentlyRenderingFiber,
    queue,
  ): any));
  // 返回初始state和触发器
  return [hook.memoizedState, dispatch];
}

更新时

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

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

const HooksDispatcherOnUpdate: Dispatcher = {
  /** 省略其它Hooks **/
   useState: updateState,
}

// 可以看到updateReducer的过程与传的initalState已经无关了,所以初始值只在第一次被使用
function updateState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  return updateReducer(basicStateReducer, (initialState: any));
}

源码路径:ReactFiberHooks.old/updateReducer

function updateReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  // 获取初始化时的 hook
  const hook = updateWorkInProgressHook();
  // 获取Hook对象上的 queue
  const queue = hook.queue;

  queue.lastRenderedReducer = reducer;

  const current: Hook = (currentHook: any);

  let baseQueue = current.baseQueue;

  const pendingQueue = queue.pending;
  if (pendingQueue !== null) {
    if (baseQueue !== null) {
      const baseFirst = baseQueue.next;
      const pendingFirst = pendingQueue.next;
      baseQueue.next = pendingFirst;
      pendingQueue.next = baseFirst;
    }
    current.baseQueue = baseQueue = pendingQueue;
    queue.pending = null;
  }

  if (baseQueue !== null) {
    const first = baseQueue.next;
    // 获取当前 State
    let newState = current.baseState;

    let newBaseState = null;
    let newBaseQueueFirst = null;
    let newBaseQueueLast = null;
    let update = first;
    do {
      const updateLane = update.lane;
      if (!isSubsetOfLanes(renderLanes, updateLane)) {
        const clone: Update<S, A> = {
          lane: updateLane,
          action: update.action,
          hasEagerState: update.hasEagerState,
          eagerState: update.eagerState,
          next: (null: any),
        };
        if (newBaseQueueLast === null) {
          newBaseQueueFirst = newBaseQueueLast = clone;
          newBaseState = newState;
        } else {
          newBaseQueueLast = newBaseQueueLast.next = clone;
        }
        currentlyRenderingFiber.lanes = mergeLanes(
          currentlyRenderingFiber.lanes,
          updateLane,
        );
        markSkippedUpdateLanes(updateLane);
      } else {
        if (newBaseQueueLast !== null) {
          const clone: Update<S, A> = {
            lane: NoLane,
            action: update.action,
            hasEagerState: update.hasEagerState,
            eagerState: update.eagerState,
            next: (null: any),
          };
          newBaseQueueLast = newBaseQueueLast.next = clone;
        }

        // Process this update.
        if (update.hasEagerState) {
          newState = ((update.eagerState: any): S);
        } else {
          const action = update.action;
          newState = reducer(newState, action);
        }
      }
      update = update.next;
    } while (update !== null && update !== first);

    if (newBaseQueueLast === null) {
      newBaseState = newState;
    } else {
      newBaseQueueLast.next = (newBaseQueueFirst: any);
    }

    if (!is(newState, hook.memoizedState)) {
      markWorkInProgressReceivedUpdate();
    }

    hook.memoizedState = newState;
    hook.baseState = newBaseState;
    hook.baseQueue = newBaseQueueLast;

    queue.lastRenderedState = newState;
  }

  const lastInterleaved = queue.interleaved;
  if (lastInterleaved !== null) {
    let interleaved = lastInterleaved;
    do {
      const interleavedLane = interleaved.lane;
      currentlyRenderingFiber.lanes = mergeLanes(
        currentlyRenderingFiber.lanes,
        interleavedLane,
      );
      markSkippedUpdateLanes(interleavedLane);
      interleaved = ((interleaved: any).next: Update<S, A>);
    } while (interleaved !== lastInterleaved);
  } else if (baseQueue === null) {
    queue.lanes = NoLanes;
  }

  const dispatch: Dispatch<A> = (queue.dispatch: any);
  // 返回新的 state,及更新 hook 的 dispatch 方法
  return [hook.memoizedState, dispatch];
}

总结

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

  • 初始化queue - mountState
  • 维护queue - dispatchAction
  • 更新queue - updateReducer

Q: 为什么不能在循环/条件语句中执行

以useState为例:
和类组件存储state不同,React并不知道我们调用了几次useState,对hooks的存储是按顺序的(参见Hook结构),一个hook对象的next指向下一个hooks。所以当我们建立示例代码中的对应关系后,Hook的结构如下:

// hook1: const [count, setCount] = useState(0) — 拿到state1
{
  memorizedState: 0
  next : {
    // hook2: const [name, setName] = useState('Zzz') - 拿到state2
    memorizedState: 'Zzz'
    next : {
      null
    }
  }
}

// hook1 => Fiber.memoizedState
// state1 === hook1.memoizedState
// hook1.next => hook2
// state2 === hook2.memoizedState

所以如果把hook1放到一个if语句中,当这个没有执行时,hook2拿到的state其实是上一次hook1执行后的state(而不是上一次hook2执行后的)。这样显然会发生错误。

useState总结

初始化:  构建dispatcher函数和初始值

更新时:

  1. 调用dispatcher函数,按序插入update(其实就是一个action)
  2. 收集update,调度一次React的更新
  3. 在更新的过程中将ReactCurrentDispatcher.current指向负责更新的Dispatcher
  4. 执行到函数组件时,useState会被重新执行,在resolve dispatcher的阶段拿到了负责更新的dispatcher。
  5. useState会拿到Hook对象,Hook.queue中存储了更新队列,依次进行更新后,即可拿到最新的state
  6. 函数组件App()执行后返回的nextChild中的count值已经是最新的了。FiberNode中的memorizedState也被设置为最新的state
  7. Fiber渲染出真实DOM。更新结束。

感谢阅读,如有错误尽请各位批评指正~