美文网首页
【译】了解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