react源码解读之更新流程

145 阅读10分钟

前言

当state发生变化,react到底干了些啥 源自这样一个问题,让我产生了要读源码的想法。

state变化,对于react这个框架,分setState(类组件)和useState(hooks)两种方式。两种方式更新过程大体上一致,区别在于创建的update结构有些不同,useState创建的update有eagerState和hasEagerState,用于在调度之前做优化策略(涉及到双缓树的设计缺陷)(这句话需要串联的知识略多,新手可先跳过)。

这里以hooks写法的onClick事件举例:

import { Button } from 'antd';
import { useState } from 'react';

function Test() {
    const [num, setNum] = useState<number>(0);

    const handleNum = () => {
        setNum(num + 1);
    };
    return (
            <div onClick={handleNum}>
                <div>{num}</div>
                <Button>+</Button>
            </div>
    );
}

export default Test;

通过onClick触发handleNum事件react会找到触发当前事件的fiber节点,fiber的hooks链表对应的useState这个hook,调用此hook的dispatch方法(需要了解fiber,react事件系统,hooks结构),对应的是dispatchSetState方法:

function dispatchSetState<S, A>(
  fiber: Fiber,
  queue: UpdateQueue<S, A>,
  action: A,
) {
  // 计算当前事件的lane
  const lane = requestUpdateLane(fiber);
  // 创建update
  const update: Update<S, A> = {
    lane,
    action,
    hasEagerState: false,
    eagerState: null,
    next: (null: any),
  };
    
  // 判断是否是render阶段
  if (isRenderPhaseUpdate(fiber)) {
    enqueueRenderPhaseUpdate(queue, update);
  } else {
  //`eagerState`优化策略针对的是单次触发状态修改的校验,`updateReducer`中最后的`Bailout`策略
  //针对的是一个状态,经过`updateQueue`链表循环计算出最新的结果后,再次的校验状态是否发生变化,
  //如果没有发生变化则进入`Bailout`策略。一个状态在一个事件回调被多次修改,就会将所有创建的`update`
  //对象添加到此`hook`对象的`updateQueue`链表中
  
    const alternate = fiber.alternate;
    // 判断双缓冲树的lane是否被清除,涉及到react性能优化问题,相同状态render三次问题
    if (
      fiber.lanes === NoLanes &&
      (alternate === null || alternate.lanes === NoLanes)
    ) {
      const lastRenderedReducer = queue.lastRenderedReducer;
      if (lastRenderedReducer !== null) {
        let prevDispatcher;
        try {
          // 当前的state,即旧的state
          const currentState: S = (queue.lastRenderedState: any);
          // 快速计算最新的state
          const eagerState = lastRenderedReducer(currentState, action);
          update.hasEagerState = true;
          update.eagerState = eagerState;
          if (is(eagerState, currentState)) {
            enqueueConcurrentHookUpdateAndEagerlyBailout(fiber, queue, update);
            return;
          }
        } catch (error) {
          // Suppress the error. It will throw again in the render phase.
        } finally {
          // 
        }
      }
    }

    const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
    if (root !== null) {
      const eventTime = requestEventTime();
      // 开启调度任务
      scheduleUpdateOnFiber(root, fiber, lane, eventTime);
      entangleTransitionUpdate(root, queue, lane);
    }
  }

  markUpdateInDevTools(fiber, lane, action);
}

整体更新流程如下:

  1. 获取更新的优先级lane
  2. 创建update对象
  3. 将update对象加入到当前fiber的更新队列
  4. 开启调度任务

获取更新的优先级lane

react中存在三种优先级:

  1. lane优先级
  2. react事件优先级
  3. scheduler优先级

lane

lane优先级:

export const TotalLanes = 31;

export const NoLanes: Lanes = /*                        */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /*                          */ 0b0000000000000000000000000000000;

export const SyncLane: Lane = /*                        */ 0b0000000000000000000000000000001;

export const InputContinuousHydrationLane: Lane = /*    */ 0b0000000000000000000000000000010;
export const InputContinuousLane: Lane = /*             */ 0b0000000000000000000000000000100;

export const DefaultHydrationLane: Lane = /*            */ 0b0000000000000000000000000001000;
export const DefaultLane: Lane = /*                     */ 0b0000000000000000000000000010000;

const TransitionHydrationLane: Lane = /*                */ 0b0000000000000000000000000100000;
const TransitionLanes: Lanes = /*                       */ 0b0000000001111111111111111000000;
const TransitionLane1: Lane = /*                        */ 0b0000000000000000000000001000000;
const TransitionLane2: Lane = /*                        */ 0b0000000000000000000000010000000;
const TransitionLane3: Lane = /*                        */ 0b0000000000000000000000100000000;
const TransitionLane4: Lane = /*                        */ 0b0000000000000000000001000000000;
const TransitionLane5: Lane = /*                        */ 0b0000000000000000000010000000000;
const TransitionLane6: Lane = /*                        */ 0b0000000000000000000100000000000;
const TransitionLane7: Lane = /*                        */ 0b0000000000000000001000000000000;
const TransitionLane8: Lane = /*                        */ 0b0000000000000000010000000000000;
const TransitionLane9: Lane = /*                        */ 0b0000000000000000100000000000000;
const TransitionLane10: Lane = /*                       */ 0b0000000000000001000000000000000;
const TransitionLane11: Lane = /*                       */ 0b0000000000000010000000000000000;
const TransitionLane12: Lane = /*                       */ 0b0000000000000100000000000000000;
const TransitionLane13: Lane = /*                       */ 0b0000000000001000000000000000000;
const TransitionLane14: Lane = /*                       */ 0b0000000000010000000000000000000;
const TransitionLane15: Lane = /*                       */ 0b0000000000100000000000000000000;
const TransitionLane16: Lane = /*                       */ 0b0000000001000000000000000000000;

const RetryLanes: Lanes = /*                            */ 0b0000111110000000000000000000000;
const RetryLane1: Lane = /*                             */ 0b0000000010000000000000000000000;
const RetryLane2: Lane = /*                             */ 0b0000000100000000000000000000000;
const RetryLane3: Lane = /*                             */ 0b0000001000000000000000000000000;
const RetryLane4: Lane = /*                             */ 0b0000010000000000000000000000000;
const RetryLane5: Lane = /*                             */ 0b0000100000000000000000000000000;

export const SomeRetryLane: Lane = RetryLane1;

export const SelectiveHydrationLane: Lane = /*          */ 0b0001000000000000000000000000000;

const NonIdleLanes: Lanes = /*                          */ 0b0001111111111111111111111111111;

export const IdleHydrationLane: Lane = /*               */ 0b0010000000000000000000000000000;
export const IdleLane: Lane = /*                        */ 0b0100000000000000000000000000000;

export const OffscreenLane: Lane = /*                   */ 0b1000000000000000000000000000000;

react事件优先级:

// 离散事件优先级,例如点击click,输入input,下拉select等事件,设为同步优先级,优先级别最高
export const DiscreteEventPriority: EventPriority = SyncLane; //
// 连续事件优先级,例如滚动scroll,拖拽drop等事件
export const ContinuousEventPriority: EventPriority = InputContinuousLane;
// 默认的优先级,例如setTimeout
export const DefaultEventPriority: EventPriority = DefaultLane;
// 闲置的事件,优先级最低
export const IdleEventPriority: EventPriority = IdleLane;

// lane转换为事件优先级
export function lanesToEventPriority(lanes: Lanes): EventPriority {
  // 获取最高优先级
  const lane = getHighestPriorityLane(lanes);
  if (!isHigherEventPriority(DiscreteEventPriority, lane)) {
    return DiscreteEventPriority;
  }
  if (!isHigherEventPriority(ContinuousEventPriority, lane)) {
    return ContinuousEventPriority;
  }
  if (includesNonIdleWork(lane)) {
    return DefaultEventPriority;
  }
  return IdleEventPriority;
}

scheduler优先级:

// 源码位置react\packages\scheduler\src\SchedulerPriorities.js
export const NoPriority = 0; //没有优先级 
export const ImmediatePriority = 1; // 立即执行任务的优先级,级别最高 
export const UserBlockingPriority = 2; // 用户阻塞的优先级 
export const NormalPriority = 3; // 正常优先级 
export const LowPriority = 4; // 较低的优先级 
export const IdlePriority = 5; // 优先级最低,闲表示任务可以闲置
//
// react事件优先级转为scheduler优先级
// 在源码的ensureRootIsScheduled方法中
let schedulerPriorityLevel;
switch (lanesToEventPriority(nextLanes)) {
  case DiscreteEventPriority:
    schedulerPriorityLevel = ImmediateSchedulerPriority;
    break;
  case ContinuousEventPriority:
    schedulerPriorityLevel = UserBlockingSchedulerPriority;
    break;
  case DefaultEventPriority:
    schedulerPriorityLevel = NormalSchedulerPriority;
    break;
  case IdleEventPriority:
    schedulerPriorityLevel = IdleSchedulerPriority;
    break;
  default:
    schedulerPriorityLevel = NormalSchedulerPriority;
    break;
}

获取当前事件的优先级

export function requestUpdateLane(fiber: Fiber): Lane {
  // 获取到当前渲染的模式:sync mode(同步模式) 或 concurrent mode(并发模式)
  const mode = fiber.mode;
  if ((mode & ConcurrentMode) === NoMode) {
    // 检查当前渲染模式是不是并发模式,等于NoMode表示不是,则使用同步模式渲染
    return (SyncLane: Lane);
  } else if (
    !deferRenderPhaseUpdateToNextBatch &&
    (executionContext & RenderContext) !== NoContext &&
    workInProgressRootRenderLanes !== NoLanes
  ) {
    // workInProgressRootRenderLanes是在任务执行阶段赋予的需要更新的fiber节点上的lane的值
    // 当新的更新任务产生时,workInProgressRootRenderLanes不为空,则表示有任务正在执行
    // 那么则直接返回这个正在执行的任务的lane,那么当前新的任务则会和现有的任务进行一次批量更新
    return pickArbitraryLane(workInProgressRootRenderLanes);
  }

  // 检查当前事件是否是过渡优先级
  // 如果是的话,则返回一个过渡优先级
  // 过渡优先级的分配规则:
  // 产生的任务A给它分配为TransitionLanes的第一位:TransitionLane1 = 0b0000000000000000000000001000000
  // 现在又产生了任务B,那么则从A的位置向左移动一位: TransitionLane2 = 0b0000000000000000000000010000000
  // 后续产生的任务则会一次向后移动,直到移动到最后一位
  // 过渡优先级共有16位:TransitionLanes = 0b0000000001111111111111111000000
  // 当所有位都使用完后,则又从第一位开始赋予事件过渡优先级
  const isTransition = requestCurrentTransition() !== NoTransition;
  if (isTransition) {
    if (currentEventTransitionLane === NoLane) {
      currentEventTransitionLane = claimNextTransitionLane();
    }
    return currentEventTransitionLane;
  }

  // 在react的内部事件中触发的更新事件,比如:onClick等,会在触发事件的时候为当前事件设置一个优先级,可以直接拿来使用
  const updateLane: Lane = (getCurrentUpdatePriority(): any);
  if (updateLane !== NoLane) {
    return updateLane;
  }

  // 在react的外部事件中触发的更新事件,比如:setTimeout等,会在触发事件的时候为当前事件设置一个优先级,可以直接拿来使用
  const eventLane: Lane = (getCurrentEventPriority(): any);
  return eventLane;
}

创建update对象

将update对象加入到当前fiber对应useState hook的更新队列

// 函数式fiber结构
fiber:{
    memoizedState:null, 用来保存hooks链表
    updateQueue:{
        lastEffect:null,//用来存储effect链表
    }
}
// hook数据结构
const hook: Hook = 
    { 
        memoizedState: null,  // 保持在内存中的局部状态.
        baseState: null, // hook.baseQueue中所有update对象合并之后的状态.
        baseQueue: null, // 存储update对象的环形链表, 只包括高于本次渲染优先级的update对象.
        queue: null, // 存储update对象的环形链表, 包括所有优先级的update对象.
        next: null, // next指针, 指向链表中的下一个hook.
    }
;

// packages\react-reconciler\src\ReactFiberHooks.new.js

function mountState(initialState) {
  # hook加载工作
  const hook = mountWorkInProgressHook();
  if (typeof initialState === 'function') {
    initialState = initialState();
  }
  hook.memoizedState = hook.baseState = initialState;
  const queue = {
    pending: null, // 等待处理的update链表
    lanes: NoLanes,
    dispatch: null, // dispatchSetState方法
    lastRenderedReducer: basicStateReducer, // 一个函数,通过action和lastRenderedState计算最新的state
    lastRenderedState: initialState, // 上一次的state
  };
  hook.queue = queue;
  const dispatch = queue.dispatch = dispatchSetState.bind(null, currentlyRenderingFiber, queue)
  # 返回值
  return [hook.memoizedState, dispatch];
}

// 加入到更新队列
export function enqueueConcurrentHookUpdate<S, A>(
  fiber: Fiber,
  queue: HookQueue<S, A>,
  update: HookUpdate<S, A>,
  lane: Lane,
) {
  const interleaved = queue.interleaved;
  if (interleaved === null) {
    update.next = update;
    //将`queue`备份到一个并发队列`concurrentQueues`之中,方便在之后将`queue.interleaved`的内容
    //转移到`queue.pending`之上。
    pushConcurrentUpdateQueue(queue);
  } else {
    update.next = interleaved.next;
    interleaved.next = update;
  }
  queue.interleaved = update;
}



queue.interleaved 用来存储update链表,这是一条单向环形链表

image.png

开启调度任务

确保调度任务执行

image.png

juejin.cn/post/705714…

image.png juejin.cn/post/728006…