likes
comments
collection
share

React项目初始化,render 源码解读

作者站长头像
站长
· 阅读数 20

我们通过 create-react-app 指令初始化react项目之后,项目的index.js文件中的代码如下,本文我们来从这两行代码入手,分析下react初始化项目过程中到底发生了什么。

初始化React项目生成的代码:

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

接下来,我们从这两行简单的代码入手,来看下React初次渲染的流程。

1.执行ReactDOM.createRoot函数

ReactDOM

ReactDOM是一个对象,包含一些属性和方法:

const ReactDOM: Object = {
  createPortal,

  findDOMNode...,

  hydrate...,

  render...,

  unstable_renderSubtreeIntoContainer...,

  unmountComponentAtNode...,

  // Temporary alias since we already shipped React 16 RC with it.
  // TODO: remove in React 17.
  unstable_createPortal...,

  ......
};
function createRoot(container: DOMContainer, options?: RootOptions): ReactRoot {
  invariant(
    isValidContainer(container),
    'unstable_createRoot(...): Target container is not a DOM element.',
  );
  const hydrate = options != null && options.hydrate === true;
  return new ReactRoot(container, true, hydrate);
}

if (enableStableConcurrentModeAPIs) {
  ReactDOM.createRoot = createRoot;
} else {
  ReactDOM.unstable_createRoot = createRoot;
}

ReactDOM.createRoot,其实就是执行createRoot这个函数,该函数通过实例化ReactRoot构造函数,返回实例化后的对象:

ReactRoot

function ReactRoot(
  container: Container,
  isConcurrent: boolean,
  hydrate: boolean,
) { 
  const root = DOMRenderer.createContainer(container, isConcurrent, hydrate);
  this._internalRoot = root;
}
ReactRoot.prototype.render = function(children: ReactNodeList,callback: ?() => mixed): Work {
  ...
};
ReactRoot.prototype.unmount = function(callback: ?() => mixed): Work {
  ...
};
ReactRoot.prototype.legacy_renderSubtreeIntoContainer = function(
  ...
};
ReactRoot.prototype.createBatch = function(): Batch {
  ...
};

在实例化ReactRoot的过程中会执行DOMRenderer.createContainer函数,该函数如下所示,目的是创建一个FiberRoot对象:

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

createContainer其实就是执行了createFiberRoot函数并返回结果对象:

export function createFiberRoot(
  containerInfo: any,
  isConcurrent: boolean,
  hydrate: boolean,
): FiberRoot {
  // Cyclic construction. This cheats the type system right now because
  // stateNode is any.
  const uninitializedFiber = createHostRootFiber(isConcurrent);

  let root;
  if (enableSchedulerTracing) {
      ......
  } else {
    root = ({
      current: uninitializedFiber,
      containerInfo: containerInfo,
      pendingChildren: null,

      earliestPendingTime: NoWork,
      latestPendingTime: NoWork,
      earliestSuspendedTime: NoWork,
      latestSuspendedTime: NoWork,
      latestPingedTime: NoWork,

      didError: false,

      pendingCommitExpirationTime: NoWork,
      finishedWork: null,
      timeoutHandle: noTimeout,
      context: null,
      pendingContext: null,
      hydrate,
      nextExpirationTimeToWorkOn: NoWork,
      expirationTime: NoWork,
      firstBatch: null,
      nextScheduledRoot: null,
    }: BaseFiberRootProperties);
  }

  uninitializedFiber.stateNode = root;

  // The reason for the way the Flow types are structured in this file,
  // Is to avoid needing :any casts everywhere interaction tracing fields are used.
  // Unfortunately that requires an :any cast for non-interaction tracing capable builds.
  // $FlowFixMe Remove this :any cast and replace it with something better.
  return ((root: any): FiberRoot);
}

FiberRoot

FiberRoot结构以及对应的结构注释如下,很多属性在后续分析过程中,我们可以慢慢理解:

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,
|};

这样,我们就清楚了,通过第一步,主要就是实例化了一个ReactRoot对象,该对象的_internalRoot属性为FiberRoot

2.执行root.render

通过对第一步的分析,我们清楚了,第一步实例化了一个ReactRoot对象并将它赋值给root变量,所以执行root.render,其实就是执行ReactRoot.renderrender函数具体如下:

ReactRoot.prototype.render = function(
  children: ReactNodeList,
  callback: ?() => mixed,
): Work {
  const root = this._internalRoot;
  const work = new ReactWork();
  callback = callback === undefined ? null : callback;
  if (__DEV__) {
    warnOnInvalidCallback(callback, 'render');
  }
  if (callback !== null) {
    work.then(callback);
  }
  DOMRenderer.updateContainer(children, root, null, work._onCommit);
  return work;
};

ReactWork

render函数中,拿到了前面我们生成的FiberRoot,接下来主要做了两件事,首先是实例化了ReactWork,接下来将rootwork变量作为参数,执行了DOMRenderer.updateContainer方法,ReactWork构造函数如下:

function ReactWork() {
  this._callbacks = null;
  this._didCommit = false;
  // TODO: Avoid need to bind by replacing callbacks in the update queue with
  // list of Work objects.
  this._onCommit = this._onCommit.bind(this);
}
ReactWork.prototype.then = function(onCommit: () => mixed): void {
  ...
};
ReactWork.prototype._onCommit = function(): void {
  ...
};

updateContainer

DOMRenderer.updateContainer函数代码如下,是render的核心流程,包含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,
  );
}

updateContainer代码中可以看出,主要流程是三个函数的执行,包含requestCurrentTimecomputeExpirationForFiber以及updateContainerAtExpirationTime

关于这三个函数的作用以及详细流程,且听下回分解。 未完待续。。。

转载自:https://juejin.cn/post/7158699492744069128
评论
请登录