React16源码解析(二)-建立更新

React源码解析系列文章欢迎阅读:
React16源码解析(一)- 图解Fiber架构
React16源码解析(二)-建立更新
React16源码解析(三)-ExpirationTime
React16源码解析(四)-Scheduler
React16源码解析(五)-更新流程渲染阶段1
React16源码解析(六)-更新流程渲染阶段2
React16源码解析(七)-更新流程渲染阶段3
React16源码解析(八)-更新流程提交阶段
正在更新中...node

在React中建立更新主要有下面三种方式:react

一、ReactDOM.render() || hydrate
二、setState
三、forceUpdatesegmentfault

注:
除了上面的还有react 16.8 引进的hooks 中的useState,这个咱们后续再讲。
hydrate是服务端渲染相关的,这块我并不会重点讲解。promise

1、ReactDOM.render()

ReactDOM.render().png

调用legacyRenderSubtreeIntoContainer()浏览器

const ReactDOM: Object = {
  // ......
  render(
    element: React$Element<any>,//传入的React组件
    container: DOMContainer,//挂载的容器节点
    callback: ?Function,//挂载后的回调函数
  ) {
    return legacyRenderSubtreeIntoContainer(
      null,
      element,
      container,
      false,
      callback,
    );
  },
  // ......
}

legacyRenderSubtreeIntoContainer

一、root = 建立ReactRoot
二、调用root.render()数据结构

function legacyRenderSubtreeIntoContainer(
  parentComponent: ?React$Component<any, any>,//null
  children: ReactNodeList,//传入进来须要挂在的class component
  container: DOMContainer,//根节点
  forceHydrate: boolean,//false
  callback: ?Function,//挂载完成后的回调函数
) {
  // ......
  // 是否存在根节点   初次渲染是不存在根节点的
  let root: Root = (container._reactRootContainer: any);
  if (!root) {
    // 一、建立ReactRoot 赋值给container._reactRootContainer和root(这里发生了不少事,一件很重要很重要的事   生成了fiber结构树。。)
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate,
    );
    // ......
  } else {
    // ......
    // 二、调用root.render()
    root.render(children, callback);
  }
  return DOMRenderer.getPublicRootInstance(root._internalRoot);
}

legacyCreateRootFromDOMContainer

一、清除全部子元素
二、建立 new ReactRoot节点架构

function legacyCreateRootFromDOMContainer(
  container: DOMContainer,//根节点
  forceHydrate: boolean,//false
): Root {
  //  服务端渲染相关 是否合并原先存在的dom节点 通常是false
  const shouldHydrate =
    forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
  // 一、清除全部子元素,经过container.lastchild循环来清除container的全部内容,由于咱们的属于首次渲染,container里边不包含任何元素
  if (!shouldHydrate) {
    let warned = false;
    let rootSibling;
    while ((rootSibling = container.lastChild)) {
      // ......
      container.removeChild(rootSibling);
    }
  }
  // Legacy roots are not async by default.
  const isConcurrent = false;
  // 二、建立 new ReactRoot节点
  return new ReactRoot(container, isConcurrent, shouldHydrate);
}

ReactRoot

从ReactRoot中, 咱们把createContainer返回值赋给了 实例的_internalRoot, 往下看createContainerapp

function ReactRoot(
  container: Container,
  isConcurrent: boolean,
  hydrate: boolean,
) {
  // 这里建立了一个FiberRoot
  const root = DOMRenderer.createContainer(container, isConcurrent, hydrate);
  this._internalRoot = root;
}

createContainer

从createContainer看出, createContainer其实是直接返回了createFiberRoot, 而createFiberRoot则是经过createHostRootFiber函数的返回值uninitializedFiber,并将其赋值在root对象的current上, 这里须要注意一个点就是,uninitializedFiber的stateNode的值是root, 即他们互相引用。dom

建立一个RootFiber -> createHostRootFiber() -> createFiber() -> new FiberNode()异步

这里建立的这个RootFiber里面的绝大部分属性都是初始值null或者是NoWork。因此具体代码我就没有贴出来了。
这里我提下有意义的点:
RootFiber上的tag会被赋值为 HostRoot。这个以后会用来判断节点类型。

还有这里建立的FiberRoot还有一个containerInfo置为ReactDOM.render第二个参数传入进来的容器节点。这个后续挂载的时候会用到。

export function createContainer(
  containerInfo: Container,
  isConcurrent: boolean,
  hydrate: boolean,
): OpaqueRoot {
  return createFiberRoot(containerInfo, isConcurrent, hydrate);
}

export function createFiberRoot(
  containerInfo: any,
  isConcurrent: boolean,
  hydrate: boolean,
): FiberRoot {
  // 一、建立了一个RootFiber
  const uninitializedFiber = createHostRootFiber(isConcurrent);
  // 二、互相引用
  // RootFiber.stateNode --> FiberRoot
  // FiberRoot.current --> RootFiber
  let root;
  root = {
    current: uninitializedFiber,
    containerInfo: containerInfo,
    // ......
  }
  uninitializedFiber.stateNode = root;
  // 三、return了这个FiberRoot
  return ((root: any): FiberRoot);
}

FiberRoot

这里牵扯到两种react中的数据结构,第一个FiberRoot,也就是上面createFiberRoot函数返回的对象。

type BaseFiberRootProperties = {|
  // root节点,render方法接收的第二个参数
  containerInfo: any,
  // 只有在持久更新中会用到,也就是不支持增量更新的平台,react-dom不会用到
  pendingChildren: any,
  // 当前应用对应的Fiber对象,是Root Fiber
  current: Fiber,

  // 一下的优先级是用来区分
  // 1) 没有提交(committed)的任务
  // 2) 没有提交的挂起任务
  // 3) 没有提交的可能被挂起的任务
  // 咱们选择不追踪每一个单独的阻塞登记,为了兼顾性能
  // The earliest and latest priority levels that are suspended from committing.
  // 最老和新的在提交的时候被挂起的任务
  earliestSuspendedTime: ExpirationTime,
  latestSuspendedTime: ExpirationTime,
  // The earliest and latest priority levels that are not known to be suspended.
  // 最老和最新的不肯定是否会挂起的优先级(全部任务进来一开始都是这个状态)
  earliestPendingTime: ExpirationTime,
  latestPendingTime: ExpirationTime,
  // The latest priority level that was pinged by a resolved promise and can
  // be retried.
  // 最新的经过一个promise被reslove而且能够从新尝试的优先级
  latestPingedTime: ExpirationTime,

  // 若是有错误被抛出而且没有更多的更新存在,咱们尝试在处理错误前同步从新从头渲染
  // 在`renderRoot`出现没法处理的错误时会被设置为`true`
  didError: boolean,

  // 正在等待提交的任务的`expirationTime`
  pendingCommitExpirationTime: ExpirationTime,
  // 已经完成的任务的FiberRoot对象,若是你只有一个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: ExpirationTime,
  // 当前更新对应的过时时间
  expirationTime: ExpirationTime,
  // List of top-level batches. This list indicates whether a commit should be
  // deferred. Also contains completion callbacks.
  // TODO: Lift this into the renderer
  // 顶层批次(批处理任务?)这个变量指明一个commit是否应该被推迟
  // 同时包括完成以后的回调
  // 貌似用在测试的时候?
  firstBatch: Batch | null,
  // root之间关联的链表结构
  nextScheduledRoot: FiberRoot | null,
|};

Fiber

这里就是createHostRootFiber函数返回的fiber对象。注意这里其实每个节点都对应一个fiber对象,不是Root专有的哦。

// Fiber对应一个组件须要被处理或者已经处理了,一个组件能够有一个或者多个Fiber
type Fiber = {|
  // 标记不一样的组件类型
  // export const FunctionComponent = 0;
  // export const ClassComponent = 1;
  // export const IndeterminateComponent = 2; // Before we know whether it is function or class
  // export const HostRoot = 3; // Root of a host tree. Could be nested inside another node.
  // export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
  // export const HostComponent = 5;
  // export const HostText = 6;
  // export const Fragment = 7;
  // export const Mode = 8;
  // export const ContextConsumer = 9;
  // export const ContextProvider = 10;
  // export const ForwardRef = 11;
  // export const Profiler = 12;
  // export const SuspenseComponent = 13;
  // export const MemoComponent = 14;
  // export const SimpleMemoComponent = 15;
  // export const LazyComponent = 16;
  // export const IncompleteClassComponent = 17;
  tag: WorkTag,

  // ReactElement里面的key
  key: null | string,

  // ReactElement.type,标签类型,也就是咱们调用`createElement`的第一个参数
  elementType: any,

  // The resolved function/class/ associated with this fiber.
  // 异步组件resolved以后返回的内容,通常是`function`或者`class`
  type: any,

  // The local state associated with this fiber.
  // 跟当前Fiber相关本地状态(好比浏览器环境就是DOM节点)
  stateNode: any,

  // 指向他在Fiber节点树中的`parent`,用来在处理完这个节点以后向上返回
  return: Fiber | null,

  // 单链表树结构
  // 指向本身的第一个子节点
  child: Fiber | null,
  // 指向本身的兄弟结构
  // 兄弟节点的return指向同一个父节点
  sibling: Fiber | null,
  index: number,

  // ref属性
  ref: null | (((handle: mixed) => void) & {_stringRef: ?string}) | RefObject,

  // 新的变更带来的新的props
  pendingProps: any, 
  // 上一次渲染完成以后的props
  memoizedProps: any,

  // 该Fiber对应的组件产生的Update会存放在这个队列里面
  updateQueue: UpdateQueue<any> | null,

  // 上一次渲染的时候的state
  memoizedState: any,

  // 一个列表,存放这个Fiber依赖的context
  firstContextDependency: ContextDependency<mixed> | null,

  // 用来描述当前Fiber和他子树的`Bitfield`
  // 共存的模式表示这个子树是否默认是异步渲染的
  // Fiber被建立的时候他会继承父Fiber
  // 其余的标识也能够在建立的时候被设置
  // 可是在建立以后不该该再被修改,特别是他的子Fiber建立以前
  mode: TypeOfMode,

  // Effect
  // 用来记录Side Effect
  // Don't change these two values. They're used by React Dev Tools.
  // export const NoEffect = /*              */ 0b00000000000;
  // export const PerformedWork = /*         */ 0b00000000001;

  // You can change the rest (and add more).
  // export const Placement = /*             */ 0b00000000010;
  // export const Update = /*                */ 0b00000000100;
  // export const PlacementAndUpdate = /*    */ 0b00000000110;
  // export const Deletion = /*              */ 0b00000001000;
  // export const ContentReset = /*          */ 0b00000010000;
  // export const Callback = /*              */ 0b00000100000;
  // export const DidCapture = /*            */ 0b00001000000;
  // export const Ref = /*                   */ 0b00010000000;
  // export const Snapshot = /*              */ 0b00100000000;

  // Update & Callback & Ref & Snapshot
  // export const LifecycleEffectMask = /*   */ 0b00110100100;

  // Union of all host effects
  // export const HostEffectMask = /*        */ 0b00111111111;

  // export const Incomplete = /*            */ 0b01000000000;
  // export const ShouldCapture = /*         */ 0b10000000000;
  effectTag: SideEffectTag,

  // 单链表用来快速查找下一个side effect
  nextEffect: Fiber | null,

  // 子树中第一个side effect
  firstEffect: Fiber | null,
  // 子树中最后一个side effect
  lastEffect: Fiber | null,

  // 表明任务在将来的哪一个时间点应该被完成
  // 不包括他的子树产生的任务
  expirationTime: ExpirationTime,

  // 快速肯定子树中是否有不在等待的变化
  childExpirationTime: ExpirationTime,

  // 在Fiber树更新的过程当中,每一个Fiber都会有一个跟其对应的Fiber
  // 咱们称他为`current <==> workInProgress`
  // 在渲染完成以后他们会交换位置
  alternate: Fiber | null,

  // 下面是调试相关的,收集每一个Fiber和子树渲染时间的

  actualDuration?: number,

  // If the Fiber is currently active in the "render" phase,
  // This marks the time at which the work began.
  // This field is only set when the enableProfilerTimer flag is enabled.
  actualStartTime?: number,

  // Duration of the most recent render time for this Fiber.
  // This value is not updated when we bailout for memoization purposes.
  // This field is only set when the enableProfilerTimer flag is enabled.
  selfBaseDuration?: number,

  // Sum of base times for all descedents of this Fiber.
  // This value bubbles up during the "complete" phase.
  // This field is only set when the enableProfilerTimer flag is enabled.
  treeBaseDuration?: number,

  // Conceptual aliases
  // workInProgress : Fiber ->  alternate The alternate used for reuse happens
  // to be the same as work in progress.
  // __DEV__ only
  _debugID?: number,
  _debugSource?: Source | null,
  _debugOwner?: Fiber | null,
  _debugIsCurrentlyTiming?: boolean,
|};

root.render()

通过上面的步骤,建立好了ReactRoot。初始化完成了。下面开始root.render。
咱们回到legacyRenderSubtreeIntoContainer函数,前面一堆讲解的是调用legacyCreateRootFromDOMContainer方法咱们获得了一个ReactRoot对象。reactRoot的原型上面咱们找到了render方法:

ReactRoot.prototype.render = function(
  children: ReactNodeList,
  callback: ?() => mixed,
): Work {
  // 这个就是咱们上面建立的FiberRoot对象
  const root = this._internalRoot;
  // ......
  DOMRenderer.updateContainer(children, root, null, work._onCommit);
  return work;
};

updateContainer

这个函数里面使用了 currentTime 和 expirationTime, currentTime是用来计算expirationTime的,expirationTime表明着优先级, 这个留在后续分析。后续紧接着调用了updateContainerAtExpirationTime。

export function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): ExpirationTime {
  // 这个current就是FiberRoot对应的RootFiber
  const current = container.current;
  const currentTime = requestCurrentTime();
  const expirationTime = computeExpirationForFiber(currentTime, current);
  return updateContainerAtExpirationTime(
    element,
    container,
    parentComponent,
    expirationTime,
    callback,
  );
}

注:这个函数在ReactFiberReconciler.js里面。

updateContainerAtExpirationTime

这里将current(即Fiber实例)提取出来, 并做为参数传入调用scheduleRootUpdate

export function updateContainerAtExpirationTime(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  expirationTime: ExpirationTime,
  callback: ?Function,
) {
  const current = container.current;
  // ......
  return scheduleRootUpdate(current, element, expirationTime, callback);
}

scheduleRootUpdate

这个函数主要执行了两个操做:
一、建立更新createUpdate并放到更新队列enqueueUpdate,建立更新的具体细节稍后再讲哈。由于待会咱们会发现其余地方也用到了。
二、个是执行sheculeWork函数,进入React异步渲染的核心:React Scheduler,这个我后续文章详细讲解。

function scheduleRootUpdate(
  current: Fiber,
  element: ReactNodeList,
  expirationTime: ExpirationTime,
  callback: ?Function,
) {
  // ......
  // 一、建立一个update对象
  const update = createUpdate(expirationTime);
  update.payload = {element};

  // ......
  // 二、将刚建立的update对象入队到fiber.updateQueue队列中
  enqueueUpdate(current, update);

  // 三、开始进入React异步渲染的核心:React Scheduler
  scheduleWork(current, expirationTime);
  return expirationTime;
}

图解

以上的过程我画了张图:
Fiber架构.png

2、setState

虽然我尚未讲解到class component 的渲染过程,可是这个不影响我如今要讨论的内容~
以下咱们调用this.setState方法的时候,调用了this.updater.enqueueSetState

Component.prototype.setState = function(partialState, callback) {
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

先无论this.updater何时被赋值的,直接看到ReactFiberClassComponent.js中的enqueueSetState,这就是咱们调用setState执行的enqueueSetState方法。

const classComponentUpdater = {
  // ......
  enqueueSetState(inst, payload, callback) {
    // inst 就是咱们调用this.setState的this,也就是classComponent实例
    // 获取到当前实例上的fiber
    const fiber = ReactInstanceMap.get(inst);
    const currentTime = requestCurrentTime();
    // 计算当前fiber的到期时间(优先级)
    const expirationTime = computeExpirationForFiber(currentTime, fiber);

    // 建立更新一个更新update
    const update = createUpdate(expirationTime);

    //payload是setState传进来的要更新的对象
    update.payload = payload;

    //callback就是setState({},()=>{})的回调函数
    if (callback !== undefined && callback !== null) {
      if (__DEV__) {
        warnOnInvalidCallback(callback, 'setState');
      }
      update.callback = callback;
    }

    // 把更新放到队列UpdateQueue
    enqueueUpdate(fiber, update);

    // 开始进入React异步渲染的核心:React Scheduler
    scheduleWork(fiber, expirationTime);
  },
  // ......
}

看到上面的代码,是否是发现和上面ReactDOM.render中scheduleRootUpdate很是的类似。其实他们就是同一个更新原理呢~

3、forceUpdate

废话很少说,先上代码。也是在ReactFiberClassComponent.js中classComponentUpdater对象中。

const classComponentUpdater = {
  // ......
  enqueueForceUpdate(inst, callback) {
    const fiber = ReactInstanceMap.get(inst);
    const currentTime = requestCurrentTime();
    const expirationTime = computeExpirationForFiber(currentTime, fiber);

    const update = createUpdate(expirationTime);

    //与setState不一样的地方
    //默认是0更新,须要改为2强制更新
    update.tag = ForceUpdate;

    if (callback !== undefined && callback !== null) {
      if (__DEV__) {
        warnOnInvalidCallback(callback, 'forceUpdate');
      }
      update.callback = callback;
    }

    enqueueUpdate(fiber, update);
    scheduleWork(fiber, expirationTime);
  },
  // ......
}

看到代码的咱们很开心,简直就是enqueueSetState的孪生兄弟。我就不详说啦。

到这里咱们总结一下上面三种更新的流程:
(1)获取节点对应的fiber对象
(2)计算currentTime
(3)根据(1)fiber和(2)currentTime计算fiber对象的expirationTime
(4)根据(3)expirationTime建立update对象
(5)将setState中要更新的对象赋值到(4)update.payload,ReactDOM.render是{element}
(6)将callback赋值到(4)update.callback
(7)update入队updateQueue
(8)进行任务调度

4、update对象

上面三种建立更新的方式中都建立了一个叫update的对象。那这个对象里面究竟是什么呢?充满好奇的咱们点开createUpdate函数瞧瞧:

export function createUpdate(expirationTime: ExpirationTime): Update<*> {
  return {
    // 过时时间
    expirationTime: expirationTime,

    // export const UpdateState = 0;
    // export const ReplaceState = 1;
    // export const ForceUpdate = 2;
    // export const CaptureUpdate = 3;
    // 指定更新的类型,值为以上几种
    // 提下CaptureUpdate,在React16后有一个ErrorBoundaries功能
    // 即在渲染过程当中报错了,能够选择新的渲染状态(提示有错误的状态),来更新页面
    // 0更新 1替换 2强制更新 3捕获性的更新
    tag: UpdateState,

    // 更新内容,好比`setState`接收的第一个参数
    // 第一次渲染ReactDOM.render接收的是payload = {element};
    payload: null,

    // 更新完成后对应的回调,`setState`,`render`都有
    callback: null,

    // 指向下一个更新
    next: null,

    // 指向下一个`side effect`,这块内容后续讲解
    nextEffect: null,
  };
}

就是返回了个简单的对象。对象每一个属性的解释我都写在上面了。

5、UpdateQueue

UpdateQueue是一个单向链表,用来存放update。每一个update用next链接。它的结构以下:

//建立更新队列
export function createUpdateQueue<State>(baseState: State): UpdateQueue<State> {
  const queue: UpdateQueue<State> = {
    // 应用更新后的state
    // 每次的更新都是在这个baseState基础上进行更新
    baseState,
    // 队列中的第一个update
    firstUpdate: null,
    // 队列中的最后一个update
    lastUpdate: null,
    // 队列中第一个捕获类型的update
    firstCapturedUpdate: null,
    // 队列中最后一个捕获类型的update
    lastCapturedUpdate: null,
    // 第一个side effect
    firstEffect: null,
    // 最后一个side effect
    lastEffect: null,
    // 第一个和最后一个捕获产生的`side effect`
    firstCapturedEffect: null,
    lastCapturedEffect: null,
  };
  return queue;
}

6、enqueueUpdate

建立了update对象以后,紧接着调用了enqueueUpdate,把update对象放到队列enqueueUpdate。同时保证current和workInProgress的updateQueue是一致的,即fiber.updateQueue和fiber.alternate.updateQueue保持一致。

export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
  // 保证current和workInProgress的updateQueue是一致的
  // alternate即workInProgress
  const alternate = fiber.alternate;
  // current的队列
  let queue1;
  // alternate的队列
  let queue2;
  // 若是alternate为空
  if (alternate === null) {
    // There's only one fiber.
    queue1 = fiber.updateQueue;
    queue2 = null;
    // 若是queue1仍为空,则初始化更新队列
    if (queue1 === null) {
      queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);
    }
  } else {
    // 若是alternate不为空,则取各自的更新队列
    queue1 = fiber.updateQueue;
    queue2 = alternate.updateQueue;
    if (queue1 === null) {
      if (queue2 === null) {
        // 初始化
        queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);
        queue2 = alternate.updateQueue = createUpdateQueue(
          alternate.memoizedState,
        );
      } else {
        // 若是queue2存在但queue1不存在的话,则根据queue2复制queue1
        queue1 = fiber.updateQueue = cloneUpdateQueue(queue2);
      }
    } else {
      if (queue2 === null) {
        // Only one fiber has an update queue. Clone to create a new one.
        queue2 = alternate.updateQueue = cloneUpdateQueue(queue1);
      } else {
        // Both owners have an update queue.
      }
    }
  }
  if (queue2 === null || queue1 === queue2) {
    // 将update放入queue1中
    appendUpdateToQueue(queue1, update);
  } else {
    // 两个队列共享的是用一个update
    // 若是两个都是空队列,则添加update
    if (queue1.lastUpdate === null || queue2.lastUpdate === null) {
      appendUpdateToQueue(queue1, update);
      appendUpdateToQueue(queue2, update);
    } else {
      // 若是两个都不是空队列,因为两个结构共享,因此只在queue1加入update
      // 在queue2中,将lastUpdate指向update
      appendUpdateToQueue(queue1, update);
      queue2.lastUpdate = update;
    }
  }

总结上面过程:
(1)queue1取的是fiber.updateQueue;

queue2取的是alternate.updateQueue

(2)若是二者均为null,则调用createUpdateQueue()获取初始队列
(3)若是二者之一为null,则调用cloneUpdateQueue()从对方中获取队列
(4)若是二者均不为null,则将update做为lastUpdate

注:两个队列共享的是同一个update。

7、scheduleWork

上面三种更新最后都调用了scheduleWork(fiber, expirationTime)进入React异步渲染的核心:React Scheduler。后续文章详细讲解。

文章若有不妥,欢迎指正~

相关文章
相关标签/搜索