React源码系列三:React.Render流程 二 之更新

1,585 阅读3分钟

在上一篇文章中,我们解析了render流程中FiberRoot的生成。而这一篇着重于在render步骤中执行的另一部分内容。会涉及到队列、更新等信息。但是不会着重于这部分,后续会进行学习。

1.legacyRenderSubtreeIntoContainer

还记得上篇文章中也是这个函数入口,开始了render的流程,而我们忽略了更新部分的代码,如下:

/**
* parentComponent :父组件,可以传入null
* children ReactDOM.render()或者ReactDOM.hydrate()中的第一个参数,可以理解为根组件。也就是render里的ReactElement对象
* container: 组件需要挂在的容器
* forceHydrate: true 为 服务端渲染,false为客户端渲染,我们研究的是客户端渲染
* callback: 组件渲染完成后需要执行的回调函数,我没有使用过这个参数,大家可以尝试一下
**/
function legacyRenderSubtreeIntoContainer(parentComponent: ?React$Component<any, any>,children: ReactNodeList,container: Container,forceHydrate: boolean,callback: ?Function,) {
  // ...省略初始化fiberRoot

  // 首次root为空,初始化root已经fiberRoot,参考上篇文章
  if (!root) {
    // .....省略创建root步骤
     fiberRoot = root._internalRoot;
    // unbatchedUpdates:初始化挂载不应该进行批处理
    unbatchedUpdates(() => {
      updateContainer(children, fiberRoot, parentComponent, callback);
    });
  } else {
    // 不是初始化挂在,直接调用updateContainer
    fiberRoot = root._internalRoot;
    updateContainer(children, fiberRoot, parentComponent, callback);
  }
  return getPublicRootInstance(fiberRoot);
}

2. unbatchedUpdates

什么是批处理,例如我们常使用的setState,当我们进行多次设置值,但是react并不会多次二更渲染,将其优化成一次更新,这个步骤就是批量渲染(batchedUpdate)。而unbatchedUpdates就是不进行批量渲染。
下面的unbatchedUpdates函数,可以看出我们只是修改了executionContext的值,代表着不进行批量处理。

// 三次设置值,会将其合并,并只执行一次
this.setState({ name: 'xiaodong' })
this.setState({ name: 'xiaoxue' })
this.setState({ name: 'xiaomei' })

function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
  // executionContext: 0b0000000
  // BatchedContext: 0b0000001
  // LegacyUnbatchedContext: 0b0001000
  const prevExecutionContext = executionContext; 
  // 且操作符和取反操作符代表除去BatchedContext这个类型
  // executionContext: 注意在ReactFiberWorkLoop一个全局对象,后续执行函数中可以直接获取
  executionContext &= ~BatchedContext; // 0b0000000 & 0b1111110
  executionContext |= LegacyUnbatchedContext; // 或操作符代表包含LegacyUnbatchedContext这个类型(遗留问题)
  try {
    return fn(a); // 调用回调函数
  } finally {
    executionContext = prevExecutionContext; // 异常时,恢复executionContext
    if (executionContext === NoContext) {
      // 刷新此批处理中计划的立即回调
      flushSyncCallbackQueue();
    }
  }
}

3. updateContainer

/**
* * children ReactDOM.render()或者ReactDOM.hydrate()中的第一个参数,可以理解为根组件。也就是render里的ReactElement对象
* container:: fiberRoot对象
* parentComponent :父组件,可以传入null
* callback: 组件渲染完成后需要执行的回调函数,我没有使用过这个参数,大家可以尝试一下
**/
export function updateContainer(element: ReactNodeList,container: OpaqueRoot,parentComponent: ?React$Component<any, any>,callback: ?Function): Lane {
  // 获取当前的rootFiber对象
  const current = container.current;
  // 获取程序运行到目前为止的时间,用于进行优先级排序
  const eventTime = requestEventTime();
  
  // 当前批量更新的配置, 是一个全局对象, 后面详细展开, 第一次执行这里返回是null
  const suspenseConfig = requestCurrentSuspenseConfig();
  // 同步直接返回 `SyncLane` = 1。以后开启并发和异步等返回的值就不一样了,目前只有同步这个模式
  const lane = requestUpdateLane(current, suspenseConfig);
  // 获取当前节点和子节点的上下文
  // 首次执行返回一个emptyContext, 是一个{}
  const context = getContextForSubtree(parentComponent); // {}
  if (container.context === null) {
    container.context = context; // fiberRoot.context = {}
  } else {
    container.pendingContext = context;
  }

  // 1. 初始化current(HostRootFiber)对象的updateQueue队列
  // 1.1 创建一个更新(update)对象
  const update = createUpdate(eventTime, lane, suspenseConfig);
  // 1.2 设置update对象的payload, 这里element需要注意(是tag=HostRoot特有的设置, 指向<App/>)
  update.payload = { element };
  // 1.3 将update对象加入到当前Fiber(这里是RootFiber)的更新对列当中
  enqueueUpdate(current, update);

  // 调度和更新current(HostRootFiber)对象
  scheduleUpdateOnFiber(current, lane, eventTime);

  return lane;
}

4. requestEventTime

获取程序运行到目前为止的时间,用于进行优先级排序。

let currentEventTime = -1;

function requestEventTime() {
  // executionContext = 0b001000, unbatchedUpdates函数中国计算的值
  // RenderContext = 0b010000;
  // CommitContext = 0b100000;
  if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
    // 我们在React内部,因此可以读取实际时间
    return now();
  }
  // 我们不在React内部,因此我们可能处于浏览器事件的中间。
  if (currentEventTime !== NoTimestamp) {
    // 对所有更新使用相同的开始时间,直到我们再次进入React
    return currentEventTime;
  }
  // 这是自React产生以来的第一次更新。计算新的开始时间。
  currentEventTime = now();
  return currentEventTime;
}

5. 结尾

render部分就已经完成了,这里面涉及的调度等会再组件更新部分进行学习。

6. 写在最后

render部分就已经完成了,这里面涉及的调度等会再组件更新部分进行学习。如果有哪些写的不正确的地方,请大家一定给我指正,谢谢。

  1. React源码系列一:React相关API
  2. React源码系列二:React Render之 FiberRoot
  3. React源码系列三:React.Render流程 二 之更新
  4. React源码系列四:React Fiber 架构
  5. React源码系列五:React Scheduler调度原理第一篇
  6. [React源码系列五:React Scheduler调度原理第二篇]....