• React源码之组件的实现与首次渲染


    react: v15.0.0

    本文讲 组件如何编译 以及 ReactDOM.render 的渲染过程。


    babel 的编译

    babel 将 React JSX 编译成 JavaScript.

    在 babel 官网写一段 JSX 代码编译结果如图:

    每个标签的创建都调用了 React.createElement.


    源码中的两种数据结构

    贯穿源码,常见的两种数据结构,有助于快速阅读源码。

    ReactElement

    结构如下:

    {
      $$typeof  // ReactElement标识符
      type      // 组件
      key
      ref
      props     // 组件属性和children
    }
    

    是 React.createElement 的返回值。

    ReactComponent

    ReactComponent 这个名字有点奇怪。

    结构如下:

    {
      _currentElement    // ReactElement
      ...
    
      // 原型链上的方法
      mountComponent,    // 组件初次加载调用
      updateComponent,   // 组件更新调用
      unmountComponent,  // 组件卸载调用
    }
    

    是 ReactCompositeComponent 的 instance 类型。其余三种构造函数 ReactDOMComponent、ReactDOMTextComponent、ReactEmptyComponent 的实例结构与其相似。


    React.createElement

    React.createElement 实际执行的是 ReactElement.createElement(目录:src/isomorphic/classic/element/ReactElement.js)。

    ReactElement.createElement 接收三个参数, 返回 ReactElement 结构。

    • type: string | Component
    • config: 标签上的属性
    • ...children: children元素集合

    重点关注 type 和 props。

    然后看 ReactElement 方法,只是做了赋值动作。

    综上,我们写的代码编译后是这样的:

    class C extends React.Component {
      render() {
        return {
          type: "div",
          props: {
            children: this.props.value,
          },
        };
      }
    }
    
    class App extends React.Component {
      render() {
        return {
          type: "div",
          props: {
            children: [
              {
                type: "span",
                props: {
                  children: "aaapppppp",
                },
              },
              "123",
              {
                type: C,
                props: {
                  value: "ccc",
                },
              },
            ]
          },
        };
      }
    }
    
    ReactDOM.render(
      {
        type: App,
        props: {},
      },
      document.getElementById("root")
    );
    

    ReactDOM.render

    先来看下 ReactDOM.render 源码的执行过程


    instantiateReactComponent

    在 _renderNewRootComponent 方法中,调用了 instantiateReactComponent(目录:src/renderers/shared/reconciler/instantiateReactComponent.js),生成了的实例结构类似于 ReactComponent。

    instantiateReactComponent 的参数是 node,node 的其中一种格式就是 ReactElement。

    根据 node & node.type 的类型,会执行不同的方法生成实例

    • ReactCompositeComponent
    • ReactDOMComponent
    • ReactDOMTextComponent
    • ReactEmptyComponent

    简化如下

    var instantiateReactComponent = function (node) {
      if (node === null || node === false) {
        return new ReactEmptyComponent(node);
      } else if (typeof node === 'object') {
        if (node.type === 'string') {
          return new ReactDOMComponent(node);
        } else {
          return new ReactCompositeComponent(node);
        }
      } else if (typeof node === 'string' || typeof node === 'number') {
        return new ReactDOMTextComponent(node);
      }
    }
    

    通过四种方式实例化后的对象基本相似

    var instance = {
      _currentElement: node,
      _rootNodeID: null,
      ...
    }
    instance.__proto__ = {
      mountComponent,
      updateComponent,
      unmountComponent,
    }
    

    四种 mountComponent 简化如下

    ReactCompositeComponent

    源码目录:src/renderers/shared/reconciler/ReactCompositeComponent.js

    mountComponent: function () {
      // 创建当前组件的实例
      this._instance = new this._currentElement.type();
    
      // 调用组件的 render 方法,得到组件的 renderedElement
      renderedElement = this._instance.render();
    
      // 调用 instantiateReactComponent,  得到 renderedElement 的实例化 ReactComponent
      this._renderedComponent = instantiateReactComponent(renderedElement);
    
      // 调用 ReactComponent.mountComponent
      return this._renderedComponent.mountComponent();
    }
    

    ReactDOMComponent

    源码目录:src/renderers/dom/shared/ReactDOMComponent.js

    react 源码中,插入 container 前使用 ownerDocument、DOMLazyTree 创建和存放节点,此处为了方便理解,使用 document.createElement 模拟。

    mountComponent: function () {
      var { type, props } = this._currentElement;
    
      // 创建dom 源码中使用 ownerDocument
      var element = document.createElement(type);
    
      // 递归children (源码中使用 DOMLazyTree 存放 并返回)
      if (props.children) {
        var childrenMarkups = props.children.map(function (node) {
          var instance = instantiateReactComponent(node);
          return instance.mountComponent();
        })
    
        element.appendChild(childrenMarkups)
      }
    
      return element;
    }
    

    ReactDOMTextComponent

    源码目录:src/renderers/dom/shared/ReactDOMTextComponent.js

    mountComponent: function () {
      return this._currentElement;
    }
    

    ReactEmptyComponent

    源码目录:src/renderers/shared/reconciler/ReactEmptyComponent.js

    mountComponent: function () {
      return null;
    }
    

    ReactDOM.render 简化

    简化如下:

    ReactDOM.render = function (nextElement, container) {
      // 添加壳子
      var nextWrappedElement = ReactElement(
        TopLevelWrapper,
        null,
        null,
        null,
        null,
        null,
        nextElement
      );
    
      // 实例化 ReactElement
      var componentInstance = instantiateReactComponent(nextElement);
    
      // 递归生成html
      var markup = componentInstance.mountComponent;
    
      // 插入真实dom
      container.innerHTML = markup;
    }
    

    总结

    1. babel 将 JSX 语法编译成 React.createElement 形式。
    2. 源码中用到了两个重要的数据结构
      • ReactElement
      • ReactComponent
    3. React.createElement 将我们写的组件处理成 ReactElement 结构。
    4. ReactDOM.render 传入 ReactElement 和 container, 渲染流程如下
      • 在 ReactElement 外套一层,生成新的 ReactElement
      • 实例化 ReactElement:var instance = instantiateReactComponent(ReactElement)
      • 递归生成 markup:var markup = instance.mountComponent()
      • 将 markup 插入 container:container.innerHTML = markup

    whosmeya.com

  • 相关阅读:
    我们在囧途之程序员转型记
    项目开发应遵循1+7还是7+1?
    自己用的一款模板解析程序(支持插件和扩展)(思路讨论和使用体验)
    设计的核心任务之一:层次的控制
    【设计 = 编码】 VS 【设计 ≠ 编码】
    从一生的角度看程序员的学习和发展
    编码质量与命名
    软件开发十年小史
    设计的核心任务之三:确保正交性
    国内外软件开发上的差距与分析
  • 原文地址:https://www.cnblogs.com/whosmeya/p/13222478.html
Copyright © 2020-2023  润新知