• 欢迎大家分享资料!前往留言板评论即可!

React源码解析(二)

合宙 模组资料网 2年前 (2021-05-15) 338次浏览 0个评论 扫描二维码

ReactDOM 渲染流程

创建更新的方式

  • ReactDOM.render || hydrate
  • setState
  • forceUpdate

ReactDOM.render

步骤
  • 创建ReactRoot
  • 创建FiberRoot和RootFiber
  • 创建更新
render源码流程
  • render (ReactDOM.js)

  • legacyRenderSubtreeIntoContainer (ReactDOM.js)

  • legacyCreateRootFromDOMContainer (ReactDOM.js)

  • ReactRoot (ReactDOM.js)

  • createContainer (react-reconciler/inline.dom)

    react-reconciler 操作与平台无关的节点调和以及操作任务调度

  • createContainer (react-reconciler/inline.dom)

  • updateContainer (react-reconciler/inline.dom)

    // expirationTime 优先级任务更新的重要字段  
    export function updateContainer(
    element: ReactNodeList,
    container: OpaqueRoot,
    parentComponent: ?React$Component<any, any>,
    callback: ?Function,
    ): ExpirationTime {
    const current = container.current;
    const currentTime = requestCurrentTime();
    const expirationTime = computeExpirationForFiber(currentTime, current);
    return updateContainerAtExpirationTime(
      element,
      container,
      parentComponent,
      expirationTime,
      callback,
    );
    }
    
  • updateContainerAtExpirationTime (react-reconciler/inline.dom)

FiberRoot

作用

  • 整个应用的起点
  • 包含应用挂载的目标节点
  • 记录整个应用更新过程的各种信息

数据结构

type BaseFiberRootProperties = {
    // root既然点,render方法的第二个参数
    containerInfo: any,
    // 只有在持久化更新中会用到,也就是不支持增量更新的平台,react-dom不会用到
    pendingChildren: any,
    // 当前应用对应的Fiber对象,是Root Fiber
    current: Fiber,
    // 以下的优先级是用来区分
    // 1) 没有提交(commited)的任务
    // 2) 没有提交的挂起任务
    // 3) 没有提交的可能被挂起的任务
    // 我们选择不追踪每个单独的阻塞登记,为了兼顾性能
    // 最老和最新的在提交的时候被挂起的任务
    earliestSuspendedTime: ExpiretionTime,
    latestSuspendedTime: ExpiretionTime,
    // 最老和最新的不确定是否会挂起的优先级(所有任务一开始进来都是这个状态)
    earliestPendingTime: ExpiretionTime,
    latestPendingTime: ExpiretionTime,
    // 最新的通过一个Promise被Resolve并且可以重新尝试的优先级
    latestPingedTime: ExpiretionTime,
    // 如果有错误被抛出并且没有更多的更新存在,我们尝试在处理错误前同步重新渲染
    // 在renderRoot出现无法处理的错误时会被设置成true
    didError: boolean,
    // 正在等待提交的任务的expirationTime
    pendingCommitExpirationTime: ExpiretionTime,
    // 已经完成任务的Fiber对象,如果只有一个Root,那它只可能时这个Root对应的Fiber,或者是null
    // 在commit阶段只会处理这个字段对应的任务
    finishedWork: Fiber | null,
    // 任务被挂起时通过setTimeout设置的返回内容,用来下一次如果有新任务挂起时清理还未触发的timeout
    timeoutHandle: TimeoutHandle | NoTimeout,
    // 顶层context对象,只有主动调用`renderSubtreeIntoContainer`时才会生效
    context: Object | null,
    pendingContext: Object | null,
    // 确定第一次渲染时是否需要融合
    hydrate: boolean,
    // 当前root上剩余的过期时间
    // TODO: 提到renderer里面去处理
    nextExpirationTimeToWorkOn: ExpiretionTime,
    // 当前更新对应的过期时间
    expirationTime: ExpiretionTime,
    // 顶层批次(批处理任务?) 这个变量知名一个commit是否应该被推迟
    // 同时包括完成之后的回调
    // 貌似在测试的时候?
    firStBatch: Batch | null,
    // root之间的链表结构
    nextScheduleRoot: FiberRoot | null,
}
Fiber

作用

  • 每个ReactElement对应一个Fiber对象
  • 记录节点的各种状态
  • 串联整个应用形成树结构

数据结构

type Fiber = {
    // 标记不同的组件类型
    tag: WorkTag,
    // RecatElement里面的key
    key: null | string,
    // ReactElement.typr,也就是我们调用`createElement`的第一个参数
    elementType: any,
    // 异步组件resolved之后返回的内容,一般是 class | function
    type: any,
    // 跟当前Fiber相关的本地状态(比如浏览器环境就是DOM节点)
    stateNode: any,
    // 指向它在Fiber节点网中的`parent`,用来在处理完这个节点之后向上返回
    return: Fiber | null,
    // 指向自己的第一个子节点
    child: Fiber | null,
    // 指向自己的兄弟节点
    sibling: Fiber | null,
    index: number,
    // ref属性
    ref: null | (((handle: mixed) => void) & {_strinfRef: ?string}) | RefObject),
    // 新的变动带来新的props
    pendingProps: any,
    // 上次渲染完成后的props
    memoizeProps: any,
    // 该Fiber对应的组件产生的Update队列
    updateQueue: updateQueue<any> | null,
    // 上次渲染时的state
    memoizedState: any,
    // 一个列表,存放这个Fiber依赖的context
    firstContextDependency: ContextDependency<mixed> | null,
    // 用来描述当前Fiber和其它子树的`Bitfield`
    // 共存的模式表示这个子树是否默认是异步渲染的
    // Fiber被创建的时候它会继承父Fiber
    // 其他标识也可以在创建时被设置
    // 但是在创建之后不应该在被修改,特别是在子Fiber创建之前
    mode: TypeOfMode,
    // 用来记录Side Effect
    effectTag: SideEffectTag,
    // 单链表用来快速查找下一个side effect
    nextEffect: Fiber | null,
    // 子树中第一个side effect
    nextEffect: Fiber | null,
    // 子树中最后一个side effect
    lastEffect: Fiber | null,
    // 过期时间
    expirationTime: ExpirationTime,
    ChildExpirationTime: ExpirationTime,
    // `current <===> workInProgress` Double Buffer
    // 存储一个Fiber,减少构建一个Fiber的性能开销
    alternate: Fiber | null,
    // 调试相关,收集每隔Fiber和子树的渲染时间
    actualDuration?: number,
    actualStartTime?: number,
    selfBaseDuration?: number,
    treeBaseDuration?: number,
    _debugID?: number,
    _debugSource?: Source | null,
    _debugOwner?: Fiber | null,
    _debugIsCurrentlyTiming?: boolean,
}
Update & UpdateQueue

作用

  • 用于记录组件状态的改变
  • 存放于UpdateQueue中
  • 多个Update可以同时存在

数据结构

type Update<State> = {
    // 更新的过期时间
    expirationTime: ExpirationTime,
    // 指定更新的类型
    // UpdateState = 0
    // ReplaceState = 1
    // ForceUpdate = 2
    // CaptureUpdate = 3 捕获的错误
    tag: 0 | 1 | 2 | 3,
    // 更新内容 比如`setState`的第一个参数
    payload: any,
    // 对应的回调,`setState` `render` 都有
    callback: (() => mixed ) | null,
    // 指向下一个更新
    next: Update<State> | null,
    // 指向下一个`side effect`
    nextEffect: Update<State> | null,
}
type UpdateQueue<State> = {
    // 每次操作完更新之后的`state`
    baseState: State,
    // 队列中的第一个/最后一个`Update`
    firstUpdate: Update<State> | null,
    lastUpdate: Update<State> | null,
    //第一个/最后一个捕获类型的`Update`
    firstCapturedUpdate: Update<State> | null,
    lastCapturedUpdate: Update<State> | null,
    // 第一个/最后一个`side effect`
    firstEffect: Update<State> | null,
    lastEffect: Update<State> | null,
     // 第一个/最后一个捕获产生的`side effect`
    firstCapturedEffect: Update<State> | null,
    lastCapturedEffect: Update<State> | null,
}
expirationTime

计算过程

((((currentTime - 2 + 5000 / 10) / 25) | 0) + 1) * 25

类型

  • Sync模式

    优先级最高的模式,创建即更新

  • 异步模式

  • 指定context

setState & forceUpdate

核心

  • 给节点的Fiber创建更新

    setState & forceUpdate 如此,reactDOM.render初始化渲染,

  • 更新的类型不同

    tag对应更新类型不同

总体流程

步骤

  • Scheduler的整体流程概述
  • 调度过程中的全局变量一览
  • 构建任务调度的概念

React源码解析(二)

scheduleWork

步骤

  • 找到更新对应的FiberRoot节点
  • 如果符合条件重置stack
  • 如果符合条件就请求工作调度

React源码解析(二)

function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
  const root = scheduleWorkToRoot(fiber, expirationTime);
  if (root === null) {
    return;
  }

  if (
    !isWorking &&
    nextRenderExpirationTime !== NoWork &&
    expirationTime > nextRenderExpirationTime
  ) {
    interruptedBy = fiber;
    resetStack();
  }
  markPendingPriorityLevel(root, expirationTime);
  if (
    !isWorking ||
    isCommitting ||
    nextRoot !== root
  ) {
    const rootExpirationTime = root.expirationTime;
    requestWork(root, rootExpirationTime);
  }
  // nestedUpdateCount 防止生命周期内进行状态更新,进行标记
  if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
    nestedUpdateCount = 0;
    invariant(
      false,
      'Maximum update depth exceeded. This can happen when a ' +
        'component repeatedly calls setState inside ' +
        'componentWillUpdate or componentDidUpdate. React limits ' +
        'the number of nested updates to prevent infinite loops.',
    );
  }
}
requestWork

核心

  • 加入到root调度队列
  • 判断是否批量更新
  • 更具expirationTime判断调度类型
function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
  addRootToSchedule(root, expirationTime);
  if (isRendering) {
    return;
  }

  // 批处理
  if (isBatchingUpdates) {
    if (isUnbatchingUpdates) {
      nextFlushedRoot = root;
      nextFlushedExpirationTime = Sync;
      performWorkOnRoot(root, Sync, false);
    }
    return;
  }

  if (expirationTime === Sync) {
    performSyncWork();
  } else {
    scheduleCallbackWithExpirationTime(root, expirationTime);
  }
}

流程未走完,更新见下节


转载请注明原文链接:React源码解析(二)
喜欢 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址