美文网首页
【译】了解React源代码-初始渲染(简单组件)1

【译】了解React源代码-初始渲染(简单组件)1

作者: nextChallenger | 来源:发表于2019-10-12 16:13 被阅读0次

    【译】了解React源代码-初始渲染(简单组件)1
    【译】了解React源代码-初始渲染(简单组件)2
    【译】了解React源代码-初始渲染(简单组件)3
    【译】了解React源代码-初始渲染(类组件)4
    【译】了解React源代码-初始渲染(类组件)5
    【译】了解React源代码-UI更新(事务)6
    【译】了解React源代码-UI更新(事务)7
    【译】了解React源代码-UI更新(单个DOM)8
    【译】了解React源代码-UI更新(DOM树)9


    UI更新本质上就是数据更改。 React提供了一种简单直观的前端编程方法,所有活动部分都以状态(state)的形式融合在一起。 我喜欢从数据结构入手,先对功能和处理逻辑有一个大致的了解,这也使代码检查变得更容易。 时不时,我对React在内部如何工作感到好奇,因此写这篇文章。

    It never hurts to have a deeper understanding down the stack, as it gives me more freedom when I need a new feature, more confidence when I want to contribute and more comfort when I upgrade.
    深入了解堆栈永远不会有伤害,因为当我需要一个新功能时,它为我提供了更多的自由,当我想做出贡献时,它给了我更多的信心,而当我升级时,它给了我更多的舒适感。

    本文将通过渲染一个简单的组件(<h1>)来理解React。 其他主题(例如,复合组件渲染,状态驱动的UI更新和组件生命周期)将在后续文章中以类似的方式进行讨论。

    本文中使用的文件:

    isomorphic/React.js
    ReactElement.createElement() 入口

    isomorphic/classic/element/ReactElement.js
    ReactElement.createElement() 实现

    renderers/dom/ReactDOM.js
    ReactDOM.render() 入口

    renderers/dom/client/ReactMount.js
    ReactDom.render() 实现

    renderers/shared/stack/reconciler/instantiateReactComponent.js
    根据元素类型创建不同类型的ReactComponents

    renderers/shared/stack/reconciler/ReactCompositeComponent.js
    ReactComponents根元素的包装器

    调用栈中使用的标签:
    - 函数调用
    = 别名
    ~ 间接函数调用

    由于不能从 flat module tree 中的import语句派生源代码文件的位置,因此我将使用@来帮助在代码片段清单中找到它们。

    最后一点,本系列基于 React 15.6.2

    从JSX到React.createElement()

    I was not consciously aware of using React.createElement(), as the existence of it is masked by JSX from a developer’s point of view.
    我没有意识到要使用React.createElement(),因为从开发人员的角度来看,它的存在已被JSX掩盖了。

    在编译时,Babel将JSX中定义的组件转换为使用适当参数调用的React.createElement()。 例如,create-react-app 附带的默认App.js:

    import React, { Component } from 'react';
    import logo from './logo.svg';
    import './App.css';
    
    class App extends Component {
      render() {
        return (
          <div className="App">
            <header className="App-header">
              <img src={logo} className="App-logo" alt="logo" />
              <h1 className="App-title">Welcome to React</h1>
            </header>
            <p className="App-intro">
              To get started, edit <code>src/App.js</code> and save to reload.
            </p>
          </div>
        );
      }
    }
    
    export default App;
    

    编译为:

    import React, { Component } from 'react';
    import logo from './logo.svg';
    import './App.css';
    
    class App extends Component {
      render() {
        return React.createElement(
          'div',
          { className: 'App' },
          React.createElement(
            'header',
            { className: 'App-header' },
            React.createElement('img', { src: logo, className: 'App-logo', alt: 'logo' }),
            React.createElement(
              'h1',
              { className: 'App-title' },
              'Welcome to React'
            )
          ),
          React.createElement(
            'p',
            { className: 'App-intro' },
            'To get started, edit ',
            React.createElement(
              'code',
              null,
              'src/App.js'
            ),
            ' and save to reload.'
          )
        );
      }
    }
    
    export default App;
    

    这是浏览器执行的真实代码。 上面的代码显示了复合组件App的定义,其中JSX,JavaScript代码中的HTML标签穿插语法(例如:<div className=”App”></div>),被转换为React.createElement()调用。
    在应用程序级别,将渲染此组件:

    ReactDOM.render(
      <App />,
      document.getElementById('root')
    );
    

    通常由一个名为“ index.js”的JS入口模块组成。

    这个嵌套的组件树太复杂了,无法成为理想的起点,因此我们现在就忘记了它,而是看一些更简单的东西-渲染一个简单的HTML元素。

    ReactDOM.render(
      <h1 style={{"color":"blue"}}>hello world</h1>,
      document.getElementById('root')
    );
    

    上面代码的转义版本是:

    ReactDOM.render(React.createElement(
      'h1',
      { style: { "color": "blue" } },
      'hello world'
    ), document.getElementById('root'));
    

    React.createElement() - 创建一个 ReactElement

    第一步并没有真正做很多事情。 它只是构造了一个ReactElement实例,其中填充了传递给调用堆栈的所有内容。 结果数据结构为:


    element数据结构.png

    调用栈为:

    React.createElement
    |=ReactElement.createElement(type, config, children)
      |-ReactElement(type,..., props)
    

    React.createElement(type, config, children)仅仅是ReactElement.createElement()的别名;

    ...
    var createElement = ReactElement.createElement;
    ...
    var React = {
    ...
      createElement: createElement,
    ...
    };
    
    module.exports = React;
    
    React@isomorphic/React.js
    

    ReactElement.createElement(type,config,children)
    1)将config中的元素复制到props
    2)将children复制到props.children
    3)将type.defaultProps复制到props;

    ...
      // 1)
      if (config != null) {
      ...extracting not interesting properties from config...
        // 其余属性添加到新的props对象
        for (propName in config) {
          if (
            hasOwnProperty.call(config, propName) &&
            !RESERVED_PROPS.hasOwnProperty(propName)
          ) {
            props[propName] = config[propName];
          }
        }
      }
    
      // 2)
      // Children可以是多个参数,并且这些参数将转移到新分配的props对象上。
      var childrenLength = arguments.length - 2;
      if (childrenLength === 1) {
        props.children = children; // scr: 将一个children存储为对象
      } else if (childrenLength > 1) {
        var childArray = Array(childrenLength);
        for (var i = 0; i < childrenLength; i++) {
          childArray[i] = arguments[i + 2]; // scr: 将多个children存储为数组
        }
    
        props.children = childArray;
      }
    
      // 3)
      // 处理默认属性
      if (type && type.defaultProps) {
        var defaultProps = type.defaultProps;
        for (propName in defaultProps) {
          if (props[propName] === undefined) {
            props[propName] = defaultProps[propName];
          }
        }
      }
    
      return ReactElement(
        type,
        key,
        ref,
        self,
        source,
        ReactCurrentOwner.current,
        props,
      );
    ...
    
    ReactElement.createElement@isomorphic/classic/element/ReactElement.js
    

    然后ReactElement(type,...,props)typeprops照原样复制到ReactElement并返回实例。

    ...
    var ReactElement = function(type, key, ref, self, source, owner, props) {
        // 这个标签可以让我们唯一地将其识别为React元素
        $$typeof: REACT_ELEMENT_TYPE,
    
        // 元素的内置属性
        type: // scr:------------------> 'h1'
        key:  // scr:------------------> 目前不感兴趣
        ref:  // scr:------------------> 目前不感兴趣
        props: {
            children:       // scr:------------------> 'hello world'
            ...other props: // scr:------------------> style: { "color": "blue" }
        },
    
        // 记录负责创建此元素的组件。
        _owner: owner, // scr: --------------> null
    };
    ...
    
    ReactElement@isomorphic/classic/element/ReactElement.js
    

    新构建的ReactElement中填充的字段将由ReactMount.instantiateReactComponent()直接使用,稍后将对其进行详细说明。 请注意,下一步还将使用ReactElement.createElement()创建一个ReactElement对象,因此我将调用此阶段的ReactElement对象ReactElement [1]

    ReactDom.render() - 渲染

    _renderSubtreeIntoContainer()
    TopLevelWrapper附加到ReactElement [1]

    下一步的目的是将ReactElement [1]与另一个ReactElement(我们将实例称为[2])包装在一起,并使用TopLevelWrapper设置ReactElement.type。 名称TopLevelWrapper解释了它的作用-包装通过render()传递的组件树的顶级元素:

    将1和2包装在一起.png

    这里的一个重要定义是·TopLevelWrapper·的定义,我在这里为CTL-f输入三颗星***,因为您稍后可能需要回到该定义。

    ...
    var TopLevelWrapper = function() {
      this.rootID = topLevelRootCounter++;
    };
    TopLevelWrapper.prototype.isReactComponent = {};
    TopLevelWrapper.prototype.render = function() {
      return this.props.child;
    };
    TopLevelWrapper.isReactTopLevelWrapper = true;
    ...
    
    TopLevelWrapper@renderers/dom/client/ReactMount.js
    

    请注意,分配给ReactElement.type的实体是一种类型(TopLevelWrapper),它将在以下呈现步骤中实例化。 (然后将通过render()this.props.child中提取ReactElement [1]。)

    构造指定对象的调用栈如下:

    ReactDOM.render
    |=ReactMount.render(nextElement, container, callback)
    |=ReactMount._renderSubtreeIntoContainer(
      parentComponent, // scr:-----------------> null
      nextElement,     // scr:-----------------> ReactElement[1]
      container,       // scr:-----------------> document.getElementById('root')
      callback'        // scr:-----------------> undefined
    )
    

    对于初始渲染,ReactMount._renderSubtreeIntoContainer()比看起来要简单,实际上,该函数中的大多数分支(用于UI更新)都被跳过了。 在逻辑进行下一步之前唯一有效的行是:

    ...
        var nextWrappedElement = React.createElement(TopLevelWrapper, {
          child: nextElement,
        });
    ...
    
    _renderSubtreeIntoContainer@renderers/dom/client/ReactMount.js
    

    现在,应该很容易看到如何使用React.createElement构造此步骤的目标对象,React.createElement是我们在上一节中检查过的函数。

    InstantiateReactComponent()
    使用ReactElement [2]创建一个ReactCompositeComponent

    此步骤的目的是为顶层组件创建一个基本的ReactCompositeComponent

    为顶层组件创建复合组件.png

    此步骤的调用栈为:

    ReactDOM.render
    |=ReactMount.render(nextElement, container, callback)
    |=ReactMount._renderSubtreeIntoContainer()
      |-ReactMount._renderNewRootComponent(
          nextWrappedElement, // scr:------------------> ReactElement[2]
          container,          // scr:------------------> document.getElementById('root')
          shouldReuseMarkup,  // scr: null from ReactDom.render()
          nextContext,        // scr: emptyObject from ReactDom.render()
        )
        |-instantiateReactComponent(
            node,             // scr:------------------> ReactElement[2]
            shouldHaveDebugID /* false */
          )
          |-ReactCompositeComponentWrapper(
              element         // scr:------------------> ReactElement[2]
            );
          |=ReactCompositeComponent.construct(element)
    

    InstantiateReactComponent是唯一值得在这里讨论的长函数。 在我们的上下文中,它将检查ReactElement [2] .type(即TopLevelWrapper)并相应地创建一个ReactCompositeComponent

    function instantiateReactComponent(node, shouldHaveDebugID) {
      var instance;
    
    ...
      } else if (typeof node === 'object') {
        var element = node;
        var type = element.type;
    ...
    
        // Special case string values
        if (typeof element.type === 'string') {
    ...
        } else if (isInternalComponentType(element.type)) {
    ...
        } else {
          instance = new ReactCompositeComponentWrapper(element);
        }
      } else if (typeof node === 'string' || typeof node === 'number') {
    ...
      } else {
    ...
      }
    
    ...
      return instance;
    }
    
    instantiateReactComponent@renderers/shared/stack/reconciler/instantiateReactComponent.js
    

    值得注意的是,新的ReactCompositeComponentWrapper()ReactCompositeComponent构造函数的直接调用:

    ...
    // To avoid a cyclic dependency, we create the final class in this module
    var ReactCompositeComponentWrapper = function(element) {
      this.construct(element);
    };
    ...
    
    ...
    Object.assign(
      ReactCompositeComponentWrapper.prototype,
      ReactCompositeComponent,
      {
        _instantiateReactComponent: instantiateReactComponent,
      },
    );
    ...
    
    ReactCompositeComponentWrapper@renderers/shared/stack/reconciler/instantiateReactComponent.js
    

    然后真正的构造函数被调用:

    construct: function(element) {
        this._currentElement = element; // scr:------------> ReactElement[2]
        this._rootNodeID = 0;
        this._compositeType = null;
        this._instance = null;
        this._hostParent = null;
        this._hostContainerInfo = null;
    
        // See ReactUpdateQueue
        this._updateBatchNumber = null;
        this._pendingElement = null;
        this._pendingStateQueue = null;
        this._pendingReplaceState = false;
        this._pendingForceUpdate = false;
    
        this._renderedNodeType = null;
        this._renderedComponent = null;
        this._context = null;
        this._mountOrder = 0;
        this._topLevelWrapper = null;
    
        // See ReactUpdates and ReactUpdateQueue.
        this._pendingCallbacks = null;
    
        // ComponentWillUnmount shall only be called once
        this._calledComponentWillUnmount = false;
      },
    
    ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.js
    

    我们将在此步骤中创建的对象命名为ReactCompositeComponent [T](顶部为T)。

    构造ReactCompositeComponent对象后,下一步是调用batchedMountComponentIntoNode,初始化ReactCompositeComponent [T]并安装它,这将在下一篇文章中详细讨论。

    (原文)Understanding The React Source Code - Initial Rendering (Simple Component) I

    (下一篇)【译】了解React源代码-初始渲染(简单组件)2

    相关文章

      网友评论

          本文标题:【译】了解React源代码-初始渲染(简单组件)1

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