美文网首页
ReactDom.render 源码阅读

ReactDom.render 源码阅读

作者: 我家小八真可爱 | 来源:发表于2019-07-01 03:15 被阅读0次

    React16 源码简介

    React16 重写了核心算法 reconciler。因为 JavaScript 引擎线程和 UI 渲染线程虽然不是一个线程,但二者是互斥的(因为JS运行结果会影响到UI线程的结果),当浏览器在处理 JavaScript 时,页面就会停止渲染,一旦 JavaScript 执行占用的时间过长,留给 UI 渲染的时间就会缩短,从而造成页面每秒渲染的帧数过低,导致页面产生明显的卡顿感。

    React 源码中 package 下的几个关键模块:

    • events:事件系统。
    • react:定义节点及其表现行为的包,代码量很少,JSX 依赖该模块。
    • react-dom:与更新和渲染相关,取决于平台(react-dom 和 react-native 中可能不同),依赖 react-reconciler、schedule。

    ReactDom.render

    前言

    ReactDom.render(
        element: React$Element<any>,
        container: DOMContainer,
        callback: ?Function,
      )
    

    ReactDom.render 的第一个参数为 ReactElement,那么 element、component 和 instance 的关系是什么呢?

    element:一个可以描述 DOM 节点或者 component 实例的对象,可以通过 React.createElement() 或者 JSX 语法创建。
    component:可以通过 class 或者 function 声明。
    instance:只有类组件才有实例,React 会自动创建实例。

    component 👇

    class App extends React.Component {
      render() {
        <div>hello!</div>
      }
    }
    

    element & instance 👇

    <App />
    

    dom element 👇

    <div>hello!</div>
    

    源码阅读

    reactdom.render.png

    1.入口

    // packages/react-dom/src/client/ReactDOM.js
    render(
        element: React$Element<any>, // 要渲染的元素
        container: DOMContainer, // 根节点
        callback: ?Function, // 渲染完执行的回调
      ) {
        return legacyRenderSubtreeIntoContainer(
          null,
          element,
          container,
          false,
          callback,
        );
      }
    

    2.创建 root,然后调用 root.render,最终返回 DOM 节点信息

    // packages/react-dom/src/client/ReactDOM.js
    function legacyRenderSubtreeIntoContainer(
      parentComponent: ?React$Component<any, any>, // null
      children: ReactNodeList, //element
      container: DOMContainer, //container
      forceHydrate: boolean, //false
      callback: ?Function, //callback
    ) {
      let root: Root = (container._reactRootContainer: any);
      // 首次渲染,root 不存在
      if (!root) {
        // Initial mount
        // 创建 root
        root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
          container,
          forceHydrate, // 在 render 中是 false,hydrate 中为 true,决定是否复用 DOM 节点
        );
        if (typeof callback === 'function') {
         // 执行回调
        }
        // Initial mount should not be batched.
        // 不使用 batchedUpdates,因为首次渲染需要尽快完成
        unbatchedUpdates(() => {
          if (parentComponent != null) {
            // ...
          } else {
            // render 中 parentComponent = null
            root.render(children, callback);
          }
        });
      } else {
        // root 存在的情况
      }
      return getPublicRootInstance(root._internalRoot);
    }
    

    2.1 创建 root

    function legacyCreateRootFromDOMContainer(
      container: DOMContainer,
      forceHydrate: boolean,
    ): Root {
      // ...
      // Legacy roots are not async by default.
      const isConcurrent = false;
      return new ReactRoot(container, isConcurrent, shouldHydrate);
    }
    
    function ReactRoot(
      container: DOMContainer,
      isConcurrent: boolean,
      hydrate: boolean,
    ) {
      const root = createContainer(container, isConcurrent, hydrate);
      this._internalRoot = root;
    }
    
    function createContainer(
      containerInfo: Container,
      isConcurrent: boolean,
      hydrate: boolean,
    ): OpaqueRoot {
      // 最终返回 FiberRoot 对象
      return createFiberRoot(containerInfo, isConcurrent, hydrate);
    }
    

    2.2 调用 root.render

    ReactRoot.prototype.render = function(
      children: ReactNodeList,
      callback: ?() => mixed,
    ): Work {
      const root = this._internalRoot;
      const work = new ReactWork();
      callback = callback === undefined ? null : callback;
      if (callback !== null) {
        work.then(callback);
      }
      updateContainer(children, root, null, work._onCommit);
      return work;
    };
    
    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,
      );
    }
    
    function updateContainerAtExpirationTime(
      element: ReactNodeList,
      container: OpaqueRoot,
      parentComponent: ?React$Component<any, any>,
      expirationTime: ExpirationTime,
      callback: ?Function,
    ) {
      const current = container.current;
      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,
    ) {
      // 创建更新
      const update = createUpdate(expirationTime);
      // Caution: React DevTools currently depends on this property
      // being called "element".
      update.payload = {element};
    
      callback = callback === undefined ? null : callback;
      if (callback !== null) {
        update.callback = callback;
      }
      // 将更新放入队列
      enqueueUpdate(current, update);
      // 开始调度。React16+提出了任务优先级的概念。
      scheduleWork(current, expirationTime);
    
      return expirationTime;
    }
    
    function createUpdate(expirationTime: ExpirationTime): Update<*> {
      return {
        expirationTime: expirationTime,
        tag: UpdateState,
        payload: null,
        callback: null,
        next: null,
        nextEffect: null,
      };
    }
    

    2.3 返回真实 DOM

    HostComponent:抽象节点,是 ClassComponent 的组成部分。具体的实现取决于 React 运行的平台。在浏览器环境下就代表 DOM 节点。
    Fiber:Fiber 是一个对象,表征 reconciliation 阶段所能拆分的最小工作单元,和 React Instance 一一对应。并通过 stateNode 属性管理 Instance 的 local state。

    // packages/react-reconciler/src/ReactFiberReconciler.js
    function getPublicRootInstance(
      container: OpaqueRoot,
    ): React$Component<any, any> | PublicInstance | null {
      // container.current 为 container 对应的 Fiber
      const containerFiber = container.current;
      if (!containerFiber.child) {
        return null;
      }
      // 判断 Fiber 子节点的类型
      switch (containerFiber.child.tag) {
        // 真实 DOM 如 div, span 等
        case HostComponent:
          return getPublicInstance(containerFiber.child.stateNode);
        default:
          // stateNode 是跟当前 Fiber 相关联的本地状态(比如浏览器环境就是真实的 DOM 节点)
          return containerFiber.child.stateNode;
      }
    }
    
    function getPublicInstance(instance: Instance): * {
      return instance;
    }
    
    参考文献

    https://reactjs.org/blog/2015/12/18/react-components-elements-and-instances.html

    相关文章

      网友评论

          本文标题:ReactDom.render 源码阅读

          本文链接:https://www.haomeiwen.com/subject/zkvkcctx.html