likes
comments
collection
share

React源码阅读(4)-支线(函数式组件)-构建细节以及更新(中)

作者站长头像
站长
· 阅读数 18

开篇

回顾一下,在构建细节和更新(上)中我们已经到了进入函数式组件Component,接下来我们就可以展开hooks讲,去看一下hooks是怎样处理更新操作。首先我们探讨一下,hooks在官网的定义中其实分为了两类,状态State Hook和副作用Effect Hook但我觉得可以再加一类那就是Ref hook,我们其实可以从fiber中定义的属性来分析对照链接-3.fiber中Hooks强相关State Hook其实很好理解就是能实现一个数据持久化且不会有副作用的hook。而Effect Hook指的就是,影响fiber节点副作用属性,会直接影响到最后commit操作的函数。总的来说api的最终指向是修改fiber中的状态和副作用,从而改变整个渲染的过程和结果。

我们这里理清一个概念,为了方便理解把,这里我们讲到的hook指的链表上的一个节点,fiber.memoizedState是一整个链表,hooks是函数式组件的维护器控制器。

State Hook

我们先讲状态hook打开在线调试(需要科学上网),再对照着我们的属性总结对照表先讲一下数据结构,fiber上的memoizedState存储的是hook形成的链表,hook本身代表节点,hook中有一个更新队列queue和基队列baseQuene这2个队列通常是queue => baseQuene,而queue中的pending又是一个环形链表update是属于pending的节点,而这里我先讲2个状态hookuseStateuseReducer

1、useState

这个api实际上也被分为了2个函数mountStateupdateState,会在初始化和对比更新分别调用,可以看上一篇文章。

1.1 mountState

通过调试当我们走到reconciler初次构建是使用的mountState,更新是updateState,可以关注前文里讲到的在renderWithHooks函数中确定更新和初始化的ReactCurrentDispatcher源码链接

React源码阅读(4)-支线(函数式组件)-构建细节以及更新(中)

我们进入到mountState,有一个很重要的函数mountWorkInProgressHook,因为在初始化一系列api中都会走到mountWorkInProgressHook这个函数去创建hook并挂载到Fiber上的memoizedState上,不管是State Hook还是Effect Hook或者Ref hook,都会按照调用顺序,来构建出一个个hook存储在memoizedState链表中,这也是我们无法去在hooks上写判断的原因。中间有一个初始化baseStatememoizedState的操作memoizedState不用我们多说就是表示当前的状态,baseState是用来合并操作使用的,最终返回了hook中的memoizedState初始值和dispatch提交函数。接下来我们就分析一下dispatchAction这个函数的调用。

function mountState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  // 创建链表和里面的属性
  const hook = mountWorkInProgressHook();
  if (typeof initialState === 'function') {
    // 值
    initialState = initialState();
  }
  // 刚创建里面的属性都为Null,这时候进行初始化,设置hook.memoizedState/hook.baseState
  hook.memoizedState = hook.baseState = initialState;
  // 设置更新队列
  const queue = (hook.queue = {
    pending: null,
    dispatch: null,
    lastRenderedReducer: basicStateReducer, //这是内置Reducer
    lastRenderedState: (initialState: any),
  });
  // 设置hook dispatch函数,实际就是调用dispatchAction,在开始就珂里化一下
  const dispatch: Dispatch<
    BasicStateAction<S>,
  > = (queue.dispatch = (dispatchAction.bind(
    null,
    currentlyRenderingFiber,
    queue,
  ): any));
  // 返回当前状态以及dispatch修改状态的函数
  return [hook.memoizedState, dispatch];
}
// 创建hook
function mountWorkInProgressHook(): Hook {
  const hook: Hook = {
    memoizedState: null,
    baseState: null,
    baseQueue: null,
    queue: null,
    next: null,
  };
  if (workInProgressHook === null) {
    // 链首的hook
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    // 将hook指向链尾
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}

dispatchAction更新,我们关注4个阶段

阶段1:初始化环形链表的update节点。

阶段2:创建queue.pending环形链表,设置节点的指向。

阶段3:性能优化,这里调用了上个函数传来的lastRenderedReducer最终的调用是在basicStateReducer,这只是一个内置的处理函数返回值或者返回回调函数处理的值。

React源码阅读(4)-支线(函数式组件)-构建细节以及更新(中) 阶段4:进入调度。

function dispatchAction<S, A>(
  fiber: Fiber,
  queue: UpdateQueue<S, A>,
  action: A,
) {
  // 1、 创建update节点
  const eventTime = requestEventTime();
  const lane = requestUpdateLane(fiber); // Legacy模式返回SyncLane
  const update: Update<S, A> = {
    lane,
    action,
    eagerReducer: null,
    eagerState: null,
    next: (null: any),
  };

  // 2. 确定环形链表的指向
  const pending = queue.pending;
  if (pending === null) {
    // 头节点
    update.next = update;
  } else {
    update.next = pending.next;
    pending.next = update;
  }
  queue.pending = update;

  const alternate = fiber.alternate;
  if (
    fiber === currentlyRenderingFiber ||
    (alternate !== null && alternate === currentlyRenderingFiber)
  ) {
    // 改变全局状态
    didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
  } else {
    // 3.性能优化
    // 判断是否为第一个update
    if (
      fiber.lanes === NoLanes &&
      (alternate === null || alternate.lanes === NoLanes)
    ) {
      const lastRenderedReducer = queue.lastRenderedReducer;
      if (lastRenderedReducer !== null) {
        let prevDispatcher;
        try {
         // 临时变量得到更新前的state
          const currentState: S = (queue.lastRenderedState: any);
          // 得到更新后的state
          const eagerState = lastRenderedReducer(currentState, action);
          // 将当前更新节点的最新提交函数和状态指向改变,也是一个优化,是在render阶段的优化,会判断
          // reducer update.eagerReducer相等就不用计算
          update.eagerReducer = lastRenderedReducer;
          update.eagerState = eagerState;
          if (is(eagerState, currentState)) {
            // 如果更新前合后的值是相等的就不需要调度了
            return;
          }
        } catch (error) {
          // Suppress the error. It will throw again in the render phase.
        } 
      }
    }
    // 4.发起调度更新, 进入`reconciler 运作流程`中的输入阶段.
    scheduleUpdateOnFiber(fiber, lane, eventTime);
  }
}

1.2.updateState

当我们组件更新的时候会走到updateState

React源码阅读(4)-支线(函数式组件)-构建细节以及更新(中)

function updateState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  return updateReducer(basicStateReducer, (initialState: any));
}

我们前面文章分析了一下调用过程,就是第一次初始化此时并不是走的函数式组件的case,而是走的IndetermiateComponent不确定组件,他调用useState的时候初始化对应的是mountState,而我们第二次去更新的时候因为tag已经被打上标记了,我们此时就会走到函数式组件更新,此时useState对应的则是updateState,然后我们的更新的函数updateState的返回值是updateReducer将更新的值和内置的提交函数传进去。

function updateReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  // 1. 通过next指针拿到hook对象,每进来一次就拿下一个
  const hook = updateWorkInProgressHook();
  const queue = hook.queue;
  // 更新提交函数
  queue.lastRenderedReducer = reducer;
  // 渲染数上的hook
  const current: Hook = (currentHook: any);
  // 被打断的情况下,上次还没有完成的update
  let baseQueue = current.baseQueue;

  // 2. 链表拼接: 将 hook.queue.pending 拼接到 current.baseQueue
  //待执行的update
  const pendingQueue = queue.pending;
  //下面的整个过程都是连接拼接
  if (pendingQueue !== null) {
    if (baseQueue !== null) {
      const baseFirst = baseQueue.next;
      const pendingFirst = pendingQueue.next;
      baseQueue.next = pendingFirst;
      pendingQueue.next = baseFirst;
    }
    // 可能发生一个更高优先级任务打断当前任务的执行 
    // 所以要将 baseQueue 也赋值给 current fiber
    current.baseQueue = baseQueue = pendingQueue;
    queue.pending = null;
  }
  // 3. 状态计算
  if (baseQueue !== null) {
    const first = baseQueue.next;
    let newState = current.baseState;

    let newBaseState = null;
    let newBaseQueueFirst = null;
    let newBaseQueueLast = null;
    let update = first;
    // 计算state
    do {
      const updateLane = update.lane;
      // 优先级提取update
      if (!isSubsetOfLanes(renderLanes, updateLane)) {
        // 优先级不够: 加入到baseQueue中, 等待下一次render再执行这次的update
        const clone: Update<S, A> = {
          lane: updateLane,
          action: update.action,
          eagerReducer: update.eagerReducer,
          eagerState: update.eagerState,
          next: (null: any),
        };
        // 把update放到下一次执行
        if (newBaseQueueLast === null) {
          newBaseQueueFirst = newBaseQueueLast = clone;
          newBaseState = newState;
        } else {
          newBaseQueueLast = newBaseQueueLast.next = clone;
        }
        // 更新优先级(mergeLanes优先级转换)
        currentlyRenderingFiber.lanes = mergeLanes(
          currentlyRenderingFiber.lanes,
          updateLane,
        );
      } else {
        // 优先级足够: 状态合并
        if (newBaseQueueLast !== null) {
          // 更新baseQueue
          // update
          const clone: Update<S, A> = {
            lane: NoLane,
            action: update.action,
            eagerReducer: update.eagerReducer,
            eagerState: update.eagerState,
            next: (null: any),
          };
          newBaseQueueLast = newBaseQueueLast.next = clone;
        }
        // 执行这次的update,计算新state
        if (update.eagerReducer === reducer) {
          // 性能优化: 相等直接取算出来的state
          newState = ((update.eagerState: any): S);
        } else {
          // 计算新的
          const action = update.action;
          // 调用reducer获取最新状态
          newState = reducer(newState, action);
        }
      }
      update = update.next;
    } while (update !== null && update !== first);

    // 更新属性
    if (newBaseQueueLast === null) {
      // newBaseQueueLast已经为空,更新baseState
      newBaseState = newState;
    } else {
      // 未处理完下次执行
      newBaseQueueLast.next = (newBaseQueueFirst: any);
    }
    if (!is(newState, hook.memoizedState)) {
      markWorkInProgressReceivedUpdate();
    }
    // 把计算之后的结果更新到workInProgressHook上
    hook.memoizedState = newState;
    hook.baseState = newBaseState;
    hook.baseQueue = newBaseQueueLast;
    queue.lastRenderedState = newState;
  }

  const dispatch: Dispatch<A> = (queue.dispatch: any);
  return [hook.memoizedState, dispatch];
}

这整个函数有三个阶段

一阶段updateWorkInProgressHook 的作用主要是取出页面fiber树中的 hooks 链表中对应的 hook 节点,挂载到 workInProgress fiber 上的 hooks 链表。

function updateWorkInProgressHook(): Hook {
  let nextCurrentHook: null | Hook;
  // 页面fiber树的Hook
  if (currentHook === null) {
    // 若 current 为 null,从 currentlyRenderingFiber.alternate 取 current
    const current = currentlyRenderingFiber.alternate;
    if (current !== null) {
      nextCurrentHook = current.memoizedState;
    } else {
      nextCurrentHook = null;
    }
  } else {
    // 否则从 current fiber 中取下一个 hook
    nextCurrentHook = currentHook.next;
  }

  // 迭代 workInProgress fiber 链表
  let nextWorkInProgressHook: null | Hook;
  if (workInProgressHook === null) {
    // workInProgressHook 说明是首次创建
    nextWorkInProgressHook 为 null = currentlyRenderingFiber.memoizedState;
  } else {
    // 取下一个 workInProgress Hook
    nextWorkInProgressHook = workInProgressHook.next;
  }

  if (nextWorkInProgressHook !== null) {
    // 只有 re-render 的情况下,nextWorkInProgressHook 不为 null,因为在之前的 render 过程中已经创建过 workInProgress hook了
    workInProgressHook = nextWorkInProgressHook;
    nextWorkInProgressHook = workInProgressHook.next;

    currentHook = nextCurrentHook;
  } else {
    // 正常情况下,currentlyRenderingFiber.memoizedState 为 null,需要到从 current fiber 中克隆一个新的创建
    invariant(
      nextCurrentHook !== null,
      'Rendered more hooks than during the previous render.',
    );
    currentHook = nextCurrentHook;

    const newHook: Hook = {
      memoizedState: currentHook.memoizedState,

      baseState: currentHook.baseState,
      baseQueue: currentHook.baseQueue,
      queue: currentHook.queue,

      next: null,
    };

    if (workInProgressHook === null) {
      // 若 workInProgressHook 为 null,作为首节点赋值给 memoizedState
      currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
    } else {
      // 将 workInProgressHook 添加到链表尾
      workInProgressHook = workInProgressHook.next = newHook;
    }
  }

  return workInProgressHook;
}

二阶段:链表拼接,将queue.pending拼接到current.baseQueue

三阶段: 状态计算优化。

2、useReducer

它和useState的实现几乎是完全一样的,都是负责创建hook初始化等,但唯一的不同就是它的reducer是由外部传入的,刚刚我们看到的useState是一个内置的。

function mountReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  const hook = mountWorkInProgressHook();
  let initialState;
  if (init !== undefined) {
    initialState = init(initialArg);
  } else {
    initialState = ((initialArg: any): S);
  }
  hook.memoizedState = hook.baseState = initialState;
  const queue = (hook.queue = {
    pending: null,
    dispatch: null,
    lastRenderedReducer: reducer,
    lastRenderedState: (initialState: any),
  });
  const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
    null,
    currentlyRenderingFiber,
    queue,
  ): any));
  return [hook.memoizedState, dispatch];
}

他们两个也都是可以互相转换的,看个例子,这两者是等价的,只是说提交的时候只能通过type提交

const [state, dispatch] = useState({ count: 0 })


const [state, dispatch] = useReducer(
  function reducer(state, action) {
    switch (action.type) {
      case 'increment':
        return { count: state.count + 1 };
      case 'decrement':
        return { count: state.count - 1 };
      default:
        throw new Error();
    }
  },
  { count: 0 },
);

总结

到这里基本的State Hook,就已经告一段落了,这一节搞懂了,后面的effect hook以及ref hook都会非常轻松。看不懂有问题可以+联系方式我们一起交流,一起卷,或者觉得文章有哪些地方可以改进的欢迎交流。