【译】了解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)
将type
和props
照原样复制到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()传递的组件树的顶级元素:
这里的一个重要定义是·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
:
此步骤的调用栈为:
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
网友评论