了解React源代码-初始渲染(简单组件)I

Gerrie van der WaltUnsplash拍摄的照片

UI更新本质上就是数据更改。 由于所有活动部分都以状态形式收敛,因此React提供了一种直观,直观的前端应用程序编程方法。 对我而言,使用React制作的App的代码审查相对容易一些,因为我喜欢从数据结构开始,以大致预期其功能和处理逻辑。 时不时,我对React在内部如何工作感到好奇,因此写这篇文章。 而且,我认为深入了解堆栈永远不会有任何伤害,因为当我需要一个新功能时,它为我提供了更多的自由,当我想要做出贡献时,它给了我更多的信心,而当我升级时,给了我更多的舒适感。

本文将通过呈现一个简单的组件(即<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包装器

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

由于显然不能从平面模块树中的import语句派生源代码文件的位置,因此在演示中,我将使用@来帮助定位代码段。

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

从JSX到`React.createElement()`

在React项目中大量使用该函数几个月后,我什至不知道该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> ); } } 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.' ) ); } } 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”的文件组成。

这个嵌套的组件树太复杂了,无法成为理想的起点,因此,让我们忘记上面的代码,现在来看一个更简单的东西-呈现一个简单的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实例,其中填充了传递给调用堆栈的所有内容。 数据结构如下:

实现此步骤目的的调用堆栈如下:

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

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

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

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

… // 1) if (config != null) { …extracting not interesting properties from config… // Remaining properties are added to a new props object for (propName in config) { if ( hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName) ) { props[propName] = config[propName]; } } } … // 1) if (config != null) { …extracting not interesting properties from config… // Remaining properties are added to a new props object for (propName in config) { if ( hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName) ) { props[propName] = config[propName]; } } } // 2) // Children can be more than one argument, and those are transferred onto // the newly allocated props object. var childrenLength = arguments.length — 2; if (childrenLength === 1) { props.children = children; // scr: one child is stored as object } else if (childrenLength > 1) { var childArray = Array(childrenLength); for (var i = 0; i < childrenLength; i++) { childArray[i] = arguments[i + 2]; // scr: multiple children are stored as array } // 2) // Children can be more than one argument, and those are transferred onto // the newly allocated props object. var childrenLength = arguments.length — 2; if (childrenLength === 1) { props.children = children; // scr: one child is stored as object } else if (childrenLength > 1) { var childArray = Array(childrenLength); for (var i = 0; i < childrenLength; i++) { childArray[i] = arguments[i + 2]; // scr: multiple children are stored as array } // 2) // Children can be more than one argument, and those are transferred onto // the newly allocated props object. var childrenLength = arguments.length — 2; if (childrenLength === 1) { props.children = children; // scr: one child is stored as object } else if (childrenLength > 1) { var childArray = Array(childrenLength); for (var i = 0; i < childrenLength; i++) { childArray[i] = arguments[i + 2]; // scr: multiple children are stored as array } props.children = childArray; } props.children = childArray; } // 3) // Resolve default props if (type && type.defaultProps) { var defaultProps = type.defaultProps; for (propName in defaultProps) { if (props[propName] === undefined) { props[propName] = defaultProps[propName]; } } } // 3) // Resolve default props if (type && type.defaultProps) { var defaultProps = type.defaultProps; for (propName in defaultProps) { if (props[propName] === undefined) { props[propName] = defaultProps[propName]; } } } // 3) // Resolve default props 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, ); … return ReactElement( type, key, ref, self, source, ReactCurrentOwner.current, props, ); … ReactElement.createElement@isomorphic/classic/element/ReactElement.js

3.然后ReactElement(type,…,props)将类型和props原样复制到ReactElement并返回实例。

… var ReactElement = function(type, key, ref, self, source, owner, props) { // This tag allow us to uniquely identify this as a React Element $$typeof: REACT_ELEMENT_TYPE, // Built-in properties that belong on the element type: // scr: --------------> 'h1' key: // scr: --------------> not of interest for now ref: // scr: --------------> not of interest for now props: { children: // scr: --------------> 'hello world' …other props: // scr: --------------> style: { “color”: “blue” } }, // Record the component responsible for creating this element. _owner: // scr: --------------> null }; … // Record the component responsible for creating this element. _owner: // scr: --------------> null }; … // Record the component responsible for creating this element. _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()传递的DOM层次结构的顶级元素):

这里的一个重要定义是TopLevelWrapper的定义:

… var TopLevelWrapper = function() { this.rootID = topLevelRootCounter++; }; TopLevelWrapper.prototype.isReactComponent = {}; TopLevelWrapper.prototype.render = function() { // scr: this function will be used to strip the wrapper later in the // rendering process … var TopLevelWrapper = function() { this.rootID = topLevelRootCounter++; }; TopLevelWrapper.prototype.isReactComponent = {}; TopLevelWrapper.prototype.render = function() { // scr: this function will be used to strip the wrapper later in the // rendering process return this.props.child; }; TopLevelWrapper.isReactTopLevelWrapper = true; … return this.props.child; }; TopLevelWrapper.isReactTopLevelWrapper = true; … return this.props.child; }; TopLevelWrapper.isReactTopLevelWrapper = true; … TopLevelWrapper@renderers/dom/client/ReactMount.js

请注意,分配给ReactElement.type的实体是一种类型(TopLevelWrapper),它将在以下呈现步骤中实例化。 然后将从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, }); … … var nextWrappedElement = React.createElement(TopLevelWrapper, { child: nextElement, }); … _renderSubtreeIntoContainer@renderers/dom/client/ReactMount.js

既然应该很容易看到这一步骤的目标对象是如何构造的,我就不再重复React.createElement了。

`instantiateReactComponent()`—使用`ReactElement [2]`创建`ReactCompositeComponent`。

这是为顶层组件创建原始ReactCompositeComponent的步骤:

此步骤的调用堆栈为:

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]的类型(即TopLevelWrapper)并相应地创建一个ReactCompositeComponent。

function instantiateReactComponent(node, shouldHaveDebugID) { var instance; … } else if (typeof node === 'object') { var element = node; var type = element.type; … 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 { … } // 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; } … 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); }; … … // 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, }, ); … … Object.assign( ReactCompositeComponentWrapper.prototype, ReactCompositeComponent, { _instantiateReactComponent: instantiateReactComponent, }, ); … ReactCompositeComponentWrapper@renderers/shared/stack/reconciler/instantiateReactComponent.js

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

construct: function(element /* scr: ------> ReactElement[2] */) { this._currentElement = element; 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; }, // ComponentWillUnmount shall only be called once this._calledComponentWillUnmount = false; }, ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.js

以下步骤还将使用InstantiateReactComponent()创建ReactCompositeComponent对象,因此我将调用此阶段的对象ReactCompositeComponent [T](顶部为T)。

构造ReactCompositeComponent对象后,下一步是调用batchedMountComponentIntoNode并初始化组件实例并将其安装,这将在下一篇文章中详细讨论。

如果您喜欢这篇文章,请为它鼓掌或在Medium上关注我。 谢谢,希望下次见。

最初发布于 holmeshe.me

From: https://hackernoon.com/understanding-the-react-source-code-initial-rendering-simple-component-i-80263fe46cf1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值