react 状态hook源码解析

  • 初次构造时,遇到use开头的Hook,就会往RootFiber上,挂载一个Hook对象并按照出现顺序构成一个链表结构, 挂载到RootFiber.memoizedState之上.
  • 在调用useState的dispatch这样的更新操作时,会往对应的hook对象上的queue上挂载update对象,并注册更新任务回调
  • 在执行更新任务时,即updateReducer,每遇到一个use开头的hook,就会执行updateReducer,调用updateWorkInProgressHook(),进行workInProgress、current移动hook链表指针,即对应的workInProgress节点就会拷贝之前的挂载在current树上的hook对象,实现状态的持久化,然后再遍历hook对象上的queue上的所有的update,进行数据更新,再此遇到use开头的hook时,再触发updateReducer,进行上述操作
    • 为什么hook不能写在if中,就是因为更新的时候不能调用updateReducer,触发对应挂载在hook上的update进行状态更新
    • 并且因为不能触发updateReducer,导致updateWorkInProgressHook进行hook链表指针移动时,如果上一个hook被if隐藏掉了,导致下一个hook实际触发的是上一个hook的updateReducer
  • 状态hook,实际调用dispatch进行更新时,是往对应的hook对象的queue上挂载对应的update,并通过scheduler注册一个任务更新回调,真正的状态更新,是在重新执行函数组件时,每遇到一个use开头的hook就会移动hook链表指针,并遍历调用挂载的update对象进行更新数据

fiber与hook相关的属性:

export type Fiber = {|
  // 1. fiber节点自身状态相关
  pendingProps: any,
  memoizedProps: any,
  updateQueue: mixed,
  memoizedState: any,

  // 2. fiber节点副作用(Effect)相关
  flags: Flags,
  nextEffect: Fiber | null,
  firstEffect: Fiber | null,
  lastEffect: Fiber | null,
|};

hook相关结构:

type Update<S, A> = {|
  lane: Lane,
  action: A,
  eagerReducer: ((S, A) => S) | null,
  eagerState: S | null,
  next: Update<S, A>,
  priority?: ReactPriorityLevel,
|};

type UpdateQueue<S, A> = {|
  pending: Update<S, A> | null,
  dispatch: (A => mixed) | null,
  lastRenderedReducer: ((S, A) => S) | null,
  lastRenderedState: S | null,
|};

export type Hook = {|
  memoizedState: any, // 保持在内存中的状态.
  baseState: any, // hook.baseQueue中所有update对象合并之后的状态.
  baseQueue: Update<any, any> | null, // 存储update对象的环形链表, 只包括高于本次渲染优先级的update对象.
  queue: UpdateQueue<any, any> | null, // 存储update对象的环形链表, 包括所有优先级的update对象.
  next: Hook | null, // 指向链表中的下一个hook
|};

  • 单个Hook拥有自己的状态hook.memoizedState和自己的更新队列hook.queue
    在这里插入图片描述

hook相关变量

// 渲染优先级
let renderLanes: Lanes = NoLanes;

// 当前正在构造的fiber, 等同于 workInProgress, 为了和当前workInProgressHook区分, 所以将其改名
let currentlyRenderingFiber: Fiber = (null: any);

// Hooks被存储在fiber.memoizedState 链表上
let currentHook: Hook | null = null; // currentHook = fiber(current).memoizedState

let workInProgressHook: Hook | null = null; // workInProgressHook = fiber(workInProgress).memoizedState

// 在function的执行过程中, 是否再次发起了更新. 只有function被完全执行之后才会重置.
// 当render异常时, 通过该变量可以决定是否清除render过程中的更新.
let didScheduleRenderPhaseUpdate: boolean = false;

// 在本次function的执行过程中, 是否再次发起了更新. 每一次调用function都会被重置
let didScheduleRenderPhaseUpdateDuringThisPass: boolean = false;

// 在本次function的执行过程中, 重新发起更新的最大次数
const RE_RENDER_LIMIT = 25;

hook处理

  • updateFunctionComponent内部使用了Hook对象, updateClassComponent内部使用了class实例
// 只保留FunctionComponent相关:
function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  const updateLanes = workInProgress.lanes;
  switch (workInProgress.tag) {
  	//函数组件
    case FunctionComponent: {
      const Component = workInProgress.type;
      //获取新props
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return updateFunctionComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes,
      );
    }
  }
}

function updateFunctionComponent(
  current,
  workInProgress,
  Component,
  nextProps: any,
  renderLanes,
) {
  // ...省略无关代码
  let context;
  let nextChildren;
  prepareToReadContext(workInProgress, renderLanes);

  // 进入Hooks相关逻辑, 最后返回下级ReactElement对象
  nextChildren = renderWithHooks(
    current,
    workInProgress,
    Component,
    nextProps,
    context,
    renderLanes,
  );
  // 进入reconcile函数, 生成下级fiber节点
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  // 返回下级fiber节点
  return workInProgress.child;
}

renderWithHooks

  • 通过这个函数,fiber和hook产生了关联
// ...省略无关代码
export function renderWithHooks<Props, SecondArg>(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: (p: Props, arg: SecondArg) => any,
  props: Props,
  secondArg: SecondArg,
  nextRenderLanes: Lanes,
): any {
  // --------------- 1. 设置全局变量 -------------------
  renderLanes = nextRenderLanes; // 当前渲染优先级
  currentlyRenderingFiber = workInProgress; // 当前fiber节点, 也就是function组件对应的fiber节点

  // 清除当前fiber的遗留状态
  workInProgress.memoizedState = null;
  workInProgress.updateQueue = null;
  workInProgress.lanes = NoLanes;

  // --------------- 2. 调用function,生成子级ReactElement对象 -------------------
  // 指定dispatcher, 区分mount和update
  ReactCurrentDispatcher.current =
    current === null || current.memoizedState === null
      ? HooksDispatcherOnMount
      : HooksDispatcherOnUpdate;
  // 执行function函数, 其中进行分析Hooks的使用
  let children = Component(props, secondArg);

  // --------------- 3. 重置全局变量,并返回 -------------------
  // 执行function之后, 还原被修改的全局变量, 不影响下一次调用
  renderLanes = NoLanes;
  currentlyRenderingFiber = (null: any);

  currentHook = null;
  workInProgressHook = null;
  didScheduleRenderPhaseUpdate = false;

  return children;
}

基本demo

import { useState, useEffect } from 'react';
export default function App() {
  // 1. useState
  const [a, setA] = useState(1);
  // 2. useEffect
  useEffect(() => {
    console.log(`effect 1 created`);
  });
  // 3. useState
  const [b] = useState(2);
  // 4. useEffect
  useEffect(() => {
    console.log(`effect 2 created`);
  });
  return (
    <>
      <button onClick={() => setA(a + 1)}>{a}</button>
      <button>{b}</button>
    </>
  );
}

  • 共使用了 4 次Hook api, 依次调用useState, useEffect, useState, useEffect.

初次构造

  • useState在fiber初次构造时对应mountState
  • useReducer在fiber初次构造时对应mountReducer
  • useEffect在fiber初次构造时分别对应mountEffect->mountEffectImpl
function mountState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  //新建hook对象并移动workInProgressHook指针
  const hook = mountWorkInProgressHook();
  //拿到初始化hook状态
  hook.memoizedState = hook.baseState = initialState;
  //创建hook.queue,basicStateReducer为useState内置的reducer
  const queue = (hook.queue = {
    pending: null,
    dispatch: null,
    // queue.lastRenderedReducer是内置函数
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: (initialState: any),
  });
 
 //创建dispatch
 const dispatch: Dispatch<
    BasicStateAction<S>,
  > = (queue.dispatch = (dispatchAction.bind(
    null,
    currentlyRenderingFiber,
    queue,
  ): any));

//返回[当前状态, dispatch函数]
  return [hook.memoizedState, dispatch];
}

function mountEffectImpl(fiberFlags, hookFlags, create, deps): void { 
  const hook = mountWorkInProgressHook();
  // ...
}

mountWorkInProgressHook

function mountWorkInProgressHook(): Hook {
  //新建一个hook对象
  const hook: Hook = {
    memoizedState: null,
    baseState: null,
    baseQueue: null,
    queue: null,
    next: null,
  };
  
  //workInProgressHook即fiber(workInProgress).memoizedState
  if (workInProgressHook === null) {
    // 链表中首个hook,currentlyRenderingFiber即workInProgress
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    // 将hook添加到链表末尾
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}

basicStateReducer

function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
  return typeof action === 'function' ? action(state) : action;
}
//对应更新state时的2种方式
dispatch({ count: 1 }); // 1.直接设置
dispatch(state => ({ count: state.count + 1 })); // 2.通过回调函数设置

  • 可见, useState就是对useReducer的基本封装, 内置了一个特殊的reducer
  • 创建hook之后返回值[hook.memoizedState, dispatch]中的dispath实际上会调用reducer函数.

mountReducer

function mountReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  // 1. 创建hook并移动指针
  const hook = mountWorkInProgressHook();
  //拿到初始状态
  let initialState;
  if (init !== undefined) {
    initialState = init(initialArg);
  } else {
    initialState = ((initialArg: any): S);
  }
  // 2. 初始化hook的属性
  // 保存状态
  hook.memoizedState = hook.baseState = initialState;
  // 2.2 设置 hook.queue
  const queue = (hook.queue = {
    pending: null,
    dispatch: null,
    // queue.lastRenderedReducer是由外部传入
    lastRenderedReducer: reducer,
    lastRenderedState: (initialState: any),
  });
  // 2.3 设置 hook.dispatch
  const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
    null,
    currentlyRenderingFiber,
    queue,
  ): any));

  // 3. 返回[当前状态, dispatch函数]
  return [hook.memoizedState, dispatch];
}

  • 初次构造时,通过调用mountWorkInProgressHook,通过链表的方式挂载每一个hook到currentlyRenderingFiber即workInProgress的memoizedState属性上
    在这里插入图片描述
    在这里插入图片描述

fiber树构造(对比更新)阶段

基本demo

import { useState } from 'react';
export default function App() {
  const [count, dispatch] = useState(0);
  return (
    <button
      onClick={() => {
        dispatch(1);
        dispatch(3);
        dispatch(2);
      }}
    >
      {count}
    </button>
  );
}

初始化时内部状态
在这里插入图片描述
点击button, 通过dispatch函数进行更新

  • 将update对象添加到hook.queue.pending环形链表
    • 环形链表的特征: 为了方便添加新元素和快速拿到队首元素(都是O(1)), 所以pending指针指向了链表中最后一个元素
    • 发起调度更新: 调用scheduleUpdateOnFiber, 进入reconciler 运作流程中的输入阶段
    • 每一个dispatch对应的update,都会挂载到这个hook.queue上
function dispatchAction<S, A>(
  fiber: Fiber,
  queue: UpdateQueue<S, A>,
  action: A,
) {
  // 1. 创建update对象
  const eventTime = requestEventTime();
  //获取优先级
  const lane = requestUpdateLane(fiber); // Legacy模式返回SyncLane
  //创建update对象
  const update: Update<S, A> = {
    lane,
    action,
    eagerReducer: null,
    eagerState: null,
    next: (null: any),
  };

  // 2. 将update对象添加到hook.queue.pending队列
  //是一个环形链表
  const pending = queue.pending;
  if (pending === null) {
    // 首个update, 创建一个环形链表指向首部
    update.next = update;
  } else {
    update.next = pending.next;
    pending.next = update;
  }
  //queue.pending始终为环形链表最后一个,.next指向首个update
  queue.pending = update;
  
  //获取workInProgress
  const alternate = fiber.alternate;
  if (
    fiber === currentlyRenderingFiber ||
    (alternate !== null && alternate === currentlyRenderingFiber)
  ) {
    // 渲染时更新, 做好全局标记
    didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
  } else {
    // 性能优化部分
    // 下面这个if判断, 能保证当前创建的update, 是`queue.pending`中第一个`update`. 为什么? 发起更新之后fiber.lanes会被改动, 如果`fiber.lanes && alternate.lanes`没有被改动, 自然就是首个update
  if (
    fiber.lanes === NoLanes &&
    (alternate === null || alternate.lanes === NoLanes)
  ) {
  	//获取reducer
    const lastRenderedReducer = queue.lastRenderedReducer;
    if (lastRenderedReducer !== null) {
      let prevDispatcher;
      
      const currentState: S = (queue.lastRenderedState: any);
   	  //获取最新state	
      const eagerState = lastRenderedReducer(currentState, action);
      // 暂存`eagerReducer`和`eagerState`, 如果在render阶段reducer==update.eagerReducer, 则可以直接使用无需再次计算
      // 即下面的updateReducer中,会用到eagerReducer来优化是否需要再次执行reducer获取state
      update.eagerReducer = lastRenderedReducer;
      update.eagerState = eagerState;
      if (is(eagerState, currentState)) {
        // 快速通道, eagerState与currentState相同, 无需调度更新
        // 注: update已经被添加到了queue.pending, 并没有丢弃. 之后需要更新的时候, 此update还是会起作用
        return;
      }
    }
  }


    // 3. 发起调度更新, 进入`reconciler 运作流程`中的输入阶段.
    scheduleUpdateOnFiber(fiber, lane, eventTime);
  }
}

  • 即便多个dispatch,会通过任务调度中的类似防抖节流的优化,只注册一次任务

ensureRootIsScheduled

//计算当前fiber的优先级
  const nextLanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  );
  //计算回掉的优先级
  const newCallbackPriority = returnNextLanesPriority();
  //没有优先级直接返回
  if (nextLanes === NoLanes) {
    return;
  }
  //首次执行为null,不会进入
  if (existingCallbackNode !== null) {
  	//existingCallbackPriority为上一次计算的结果
    const existingCallbackPriority = root.callbackPriority;
    //如果再次调用,同个节点的任务优先级相同,直接返回
    if (existingCallbackPriority === newCallbackPriority) {
      return;
    }
    //如果同个节点的任务优先级不同,取消上次的任务
    cancelCallback(existingCallbackNode);
  }
...

updateFunctionComponent->renderWithHooks

  • 当dispatch发起调度进行更新后,会克隆RootFiber上的hook链表,然后遍历每一个hook上的queue上的update对象,挂在的update操作由上面的dispatch函数完成

  • 每次遇到useState在fiber对比更新对应调用updateState->updateReducer

    • 这也解释了为什么hook不能写在if中,因为如果没有遇到useState,就不会调用此循环操作进行数据更新
  • 每次遇到useEffect在fiber对比更新对应调用updateEffect->updateEffectImpl

// ----- 状态Hook --------
function updateReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  //从current.memoizedState上克隆hook并移动指针,获取workInProgress
  const hook = updateWorkInProgressHook();
  const queue = hook.queue;
  queue.lastRenderedReducer = reducer;
  const current: Hook = (currentHook: any);
  let baseQueue = current.baseQueue;

// 2. 链表拼接: 将 hook.queue.pending 拼接到 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上
    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;
	//在更新时,每遇到useState,都会执行此操作循环update对象
	//这也解释了为什么hook不能写在if中,因为如果没有遇到useState,就不会调用此循环操作进行数据更新
    do {
      const updateLane = update.lane;
      // 3.1 优先级提取update
      if (!isSubsetOfLanes(renderLanes, updateLane)) {
        // 优先级不够: 加入到baseQueue中, 等待下一次render
        const clone: Update<S, A> = {
          lane: updateLane,
          action: update.action,
          eagerReducer: update.eagerReducer,
          eagerState: update.eagerState,
          next: (null: any),
        };
        //拼接操作
        if (newBaseQueueLast === null) {
          //更新newBaseQueueFirst
          newBaseQueueFirst = newBaseQueueLast = clone;
          newBaseState = newState;
        } else {
          //更新newBaseQueueLast
          newBaseQueueLast = newBaseQueueLast.next = clone;
        }
        currentlyRenderingFiber.lanes = mergeLanes(
          currentlyRenderingFiber.lanes,
          updateLane,
        );
        markSkippedUpdateLanes(updateLane);
      } else {
        // 优先级足够: 状态合并
        if (newBaseQueueLast !== null) {
          // 更新baseQueue
          const clone: Update<S, A> = {
            lane: NoLane,
            action: update.action,
            eagerReducer: update.eagerReducer,
            eagerState: update.eagerState,
            next: (null: any),
          };
          //更新newBaseQueueLast
          newBaseQueueLast = newBaseQueueLast.next = clone;
        }
        if (update.eagerReducer === reducer) {
          // 性能优化: 如果存在 update.eagerReducer, 直接使用update.eagerState.避免重复调用reducer
          newState = ((update.eagerState: any): S);
        } else {
          const action = update.action;
          // 调用reducer获取最新状态
          newState = reducer(newState, action);
        }
      }
      //切换下一个update
      update = update.next;
    } while (update !== null && update !== first);

    // 3.2. 更新属性
    if (newBaseQueueLast === null) {
      newBaseState = newState;
    } else {
      newBaseQueueLast.next = (newBaseQueueFirst: any);
    }
    if (!is(newState, hook.memoizedState)) {
      markWorkInProgressReceivedUpdate();
    }
    // 把计算之后的结果更新到workInProgressHook上
    hook.memoizedState = newState;
    //当存在低优先级的update时,会保存低优先级update的状态
    hook.baseState = newBaseState;
    //当有低优先级更新时,用来保存update链表,留到下次更新调用
    //也是个环形链表
    //当没有低优先级update时,newBaseQueueLast为null
    hook.baseQueue = newBaseQueueLast;
    queue.lastRenderedState = newState;
  }

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


}

// ----- 副作用Hook --------
function updateEffectImpl(fiberFlags, hookFlags, create, deps): void {
  const hook = updateWorkInProgressHook();
  // ...
}

updateWorkInProgressHook

  • currentHook和workInProgressHook两个指针同时向后移动进行hook的克隆操作
  • 从currentHook克隆而来的newHook.next=null, 进而workInProgressHook链表需要完全重建.
function updateWorkInProgressHook(): Hook {
  // 1. 移动currentHook指针,即current.memoizedState上的hook链表
  let nextCurrentHook: null | Hook;
  if (currentHook === null) {
    const current = currentlyRenderingFiber.alternate;
    if (current !== null) {
      //首次克隆
      nextCurrentHook = current.memoizedState;
    } else {
      nextCurrentHook = null;
    }
  } else {
    //移动指针
    nextCurrentHook = currentHook.next;
  }

  // 2. 移动workInProgressHook指针
  let nextWorkInProgressHook: null | Hook;
  if (workInProgressHook === null) {
  	//currentlyRenderingFiber即workInProgress
    nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
  } else {
    //因克隆hook的next为null,所以nextWorkInProgressHook为null,继续走克隆hook流程
    nextWorkInProgressHook = workInProgressHook.next;
  }

  if (nextWorkInProgressHook !== null) {
    // 渲染时更新
  } else {
    currentHook = nextCurrentHook;
    // 3. 克隆currentHook作为新的workInProgressHook.
    // 随后逻辑与mountWorkInProgressHook一致
    const newHook: Hook = {
      memoizedState: currentHook.memoizedState,

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

      next: null, // 注意next指针是null
    };
    if (workInProgressHook === null) {
     
      currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
    } else {
     //继续根据current.memoizedState上挂载的hook链表进行克隆并移动workInProgressHook指针
      workInProgressHook = workInProgressHook.next = newHook;
    }
  }
  return workInProgressHook;
}

  • 因为更新会拿到上一次的hook对象,所以状态并不会丢失
  • 利用fiber树内部的双缓冲技术, 实现了Hook从current到workInProgress转移, 进而实现了Hook状态的持久化

在这里插入图片描述

克隆hook操作
在这里插入图片描述

最后调用dispatch进行更新后
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值