Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React创建更新 #1

Open
lz-lee opened this issue Jul 25, 2019 · 0 comments
Open

React创建更新 #1

lz-lee opened this issue Jul 25, 2019 · 0 comments

Comments

@lz-lee
Copy link
Owner

lz-lee commented Jul 25, 2019

React 创建更新的过程

创建更新的方式

  • 初始渲染

    ReactDOM.render 、ReactDOM.hydrate

  • 组件内更新

    setState

    forceUpdate

    replaceState(即将被舍弃)

ReactDom.render

步骤

  • 创建 ReactRoot 包含React整个应用最顶点的对象
  • 创建 FiberRoot 和 RootFiber
  • 创建更新 update,用来更新调度,进入调度后 setState 或 ReactDOM.render 的后续操作都的调度器去管理的

初始渲染

ReactDOM对象有renderhydrate两个方法,render 方法是用在浏览器环境内, hydrate 用于服务端渲染,区别在于调用 legacyRenderSubtreeIntoContainer 方法第四个参数 falsetrue.

  const ReactDOM = {
    // ...
    hydrate(element: React$Node, container: DOMContainer, callback: ?Function) {
      // TODO: throw or warn if we couldn't hydrate?
      return legacyRenderSubtreeIntoContainer(
        null,
        element,
        container,
        true,
        callback,
      );
    },

    render(
      element: React$Element<any>, // React element
      container: DOMContainer, // 挂在节点
      callback: ?Function, // 渲染结束的callback
    ) {
      return legacyRenderSubtreeIntoContainer(
        null,
        element,
        container,
        false,
        callback,
      );
    }
  }

legacyRenderSubtreeIntoContainer 作用主要是生成 ReactRoot 并调用 ReactRoot 实例的 render 方法

  function legacyRenderSubtreeIntoContainer(
    parentComponent: ?React$Component<any, any>,
    children: ReactNodeList,
    container: DOMContainer,
    forceHydrate: boolean,
    callback: ?Function,
  ) {
    ...
    // TODO: Without `any` type, Flow says "Property cannot be accessed on any
    // member of intersection type." Whyyyyyy.
    let root: Root = (container._reactRootContainer: any);
    // container 就是首次渲染传入的<div id="root">, 这个dom 肯定不存在 _reactRootContainer 属性
    if (!root) {
      // Initial mount
      // 初次渲染,创建ReactRoot,并创建FiberRoot
      root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
        container,
        forceHydrate,
      );
      // 封装回调
      if (typeof callback === 'function') {
       ...
      }
      // Initial mount should not be batched.
      // 初始渲染不是批量更新
      DOMRenderer.unbatchedUpdates(() => {
         // render, hydrate 传入的 parentComponent 都是 null
        if (parentComponent != null) {
          root.legacy_renderSubtreeIntoContainer(
            parentComponent,
            children,
            callback,
          );
        } else {
          // 调用 ReactRoot 的render方法
          root.render(children, callback);
        }
      });
    } else {
      // 更新逻辑
      if (typeof callback === 'function') {
        const originalCallback = callback;
        callback = function() {
          const instance = DOMRenderer.getPublicRootInstance(root._internalRoot);
          originalCallback.call(instance);
        };
      }
      // Update
      if (parentComponent != null) {
        root.legacy_renderSubtreeIntoContainer(
          parentComponent,
          children,
          callback,
        );
      } else {
        root.render(children, callback);
      }
    return DOMRenderer.getPublicRootInstance(root._internalRoot);
  }

legacyCreateRootFromDOMContainer 函数的作用判断是不是 hydrate 渲染,ReactDOM.render 当然为 false,接着会判断 rootElement 有没有 ROOT_ATTRIBUTE_NAMEdata-reactroot 属性来判断是不是服务端渲染,如果不是则 while 循环将 root 所有子节点全部删掉,返回new ReactRoot(container, isConcurrent, shouldHydrate)

  function legacyCreateRootFromDOMContainer(
    container: DOMContainer, // ReactDOM.render 传入的 root
    forceHydrate: boolean, // ReactDOM.render 为false
  ): Root {
    const shouldHydrate =
      forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
    // First clear any existing content.
    if (!shouldHydrate) {
      let warned = false;
      let rootSibling;
      while ((rootSibling = container.lastChild)) {
        if (__DEV__) {
          ...
        }
        // 把 root 的所有子节点删掉
        container.removeChild(rootSibling);
      }
    }
    if (__DEV__) {
      ...
    }
    // Legacy roots are not async by default.
    // root 节点创建时同步的,isConcurrent: false
    const isConcurrent = false;
    return new ReactRoot(container, isConcurrent, shouldHydrate);
  }

接着看ReactRoot构造函数,构造函数里调用了 ReactFiberReconciler 里的 createContainer 方法,这个方法调用 createFiberRoot 生成一个 FiberRoot 对象,最后挂载到实例的 _internalRoot 上,

  function ReactRoot(
    container: Container,
    isConcurrent: boolean,
    hydrate: boolean,
  ) {
    const root = DOMRenderer.createContainer(container, isConcurrent, hydrate);
    this._internalRoot = root;
  }

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

legacyRenderSubtreeIntoContainer 方法里 root.render 调用的最终 ReactRoot 构造函数的 protoType 上定义了 render 方法,而它调用的是 ReactFiberReconciler 里的 updateContainer

  ReactRoot.prototype.render = function(
    children: ReactNodeList,
    callback: ?() => mixed,
  ): Work {
    // FiberRoot
    // FiberRoot 是根据 ReactDOM.render 方法的第二个参数创建出来的,创建的过程中同时创建 RootFiber, RootFiber.stateNode = FiberRoot, FiberRoot.current = RootFiber
    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;
  };

DOMRenderer.updateContainer 中计算出一个 expirationTime 传入了 updateContainerAtExpirationTime.

  // ReactFiberReconciler.js
  export function updateContainer(
    element: ReactNodeList, // <App />
    container: OpaqueRoot, // FiberRoot
    parentComponent: ?React$Component<any, any>,
    callback: ?Function,
  ): ExpirationTime {
    // RootFiber
    const current = container.current;
    // 创建一个时间差
    const currentTime = requestCurrentTime();
    // 计算出一个时间,ConcurrentMode 会用到
    const expirationTime = computeExpirationForFiber(currentTime, current);
    return updateContainerAtExpirationTime(
      element,
      container,
      parentComponent,
      expirationTime,
      callback,
    );
  }

FiberRootRootFiber 接下来会讲到,先看 updateContainerAtExpirationTime 创建更新的过程,调用scheduleRootUpdate 方法将 RootFiberelement、计算出的expirationTimecallback 传入,在这个方法里,首先会调用createUpdate 创建一个 update 对象,enqueueUpdate 方法将 update 对象加入到 fiber 对象上的 updateQueue 里,scheduleWork 即开始执行调度。创建更新的过程到此为止。

  export function updateContainerAtExpirationTime(
    element: ReactNodeList, // <App />
    container: OpaqueRoot,
    parentComponent: ?React$Component<any, any>,
    expirationTime: ExpirationTime,
    callback: ?Function,
  ) {
    // TODO: If this is a nested container, this won't be the root.
    const current = container.current;

    if (__DEV__) {
      ...
    }
    // 目前版本React拿不到context,因为 parentComponent 为null
    const context = getContextForSubtree(parentComponent);
    if (container.context === null) {
      container.context = context;
    } else {
      container.pendingContext = context;
    }

    return scheduleRootUpdate(current, element, expirationTime, callback);
  }

  function scheduleRootUpdate(
    current: Fiber,
    element: ReactNodeList,
    expirationTime: ExpirationTime,
    callback: ?Function,
  ) {
    if (__DEV__) {
      ...
    }
    // update对象是用来标记React应用中需要更新的节点
    const update = createUpdate(expirationTime);
    // Caution: React DevTools currently depends on this property
    // being called "element".
    // 设置update的相关属性
    update.payload = {element};

    callback = callback === undefined ? null : callback;
    if (callback !== null) {
      warningWithoutStack(
        typeof callback === 'function',
        'render(...): Expected the last optional `callback` argument to be a ' +
          'function. Instead received: %s.',
        callback,
      );
      update.callback = callback;
    }
    // 把update加入到 fiber 的updateQueue 里,一个节点上可能会产生很多次更新,需要 batchUpdates (批量更新)
    enqueueUpdate(current, update);

    // 有更新产生,告诉 React 开始调度
    scheduleWork(current, expirationTime);
    return expirationTime;
  }

ReactDOM.render 总结

  • 初次渲染 传入 APP 组件和 getElementById(root) 执行 ReactDOM.render 返回并执行 legacyRenderSubtreeIntoContainer

    • legacyRenderSubtreeIntoContainer 方法调用 legacyCreateRootFromDOMContainer 方法把 getElementById(root) 里的子节点清空,并把返回值 new ReactRoot 挂载到 getElementById(root) 节点的 _reactRootContainer 属性上

    • ReactRoot 生成实例时调用 react-reconcile 模块的 createContainer 传入 getElementById(root) 执行 createFiberRoot 生成一个 FiberRoot 对象挂载到实例的 _internalRoot

    • legacyRenderSubtreeIntoContainer 方法里 root.render 方法实际是调用 ReactRoot 原型上的render方法

    • ReactRoot.prototype.render 把子节点和实例生成的 _internalRoot FiberRoot 对象传入 react-reconcile 模块的 updateContainer 中

    • 在 updateContainer 中 react 计算出一个 expirationTime 传入 updateContainerAtExpirationTime 调用 scheduleRootUpdate 中做三件事

      • createUpddate 创建update对象来标记 react 需要更新的点

      • enqueueUpdate 将 update 加入到 RootFiber 的 updateQueue 中

      • scheduleWork 根据任务的优先级进行调度更新

下一篇

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant