【譯】了解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更新本質上就是數(shù)據(jù)更改。 React提供了一種簡單直觀的前端編程方法,所有活動部分都以狀態(tài)(state)的形式融合在一起。 我喜歡從數(shù)據(jù)結構入手,先對功能和處理邏輯有一個大致的了解,這也使代碼檢查變得更容易。 時不時,我對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。 其他主題(例如,復合組件渲染,狀態(tài)驅動的UI更新和組件生命周期)將在后續(xù)文章中以類似的方式進行討論。
本文中使用的文件:
isomorphic/React.js
ReactElement.createElement()入口
isomorphic/classic/element/ReactElement.js
ReactElement.createElement()實現(xiàn)
renderers/dom/ReactDOM.js
ReactDOM.render()入口
renderers/dom/client/ReactMount.js
ReactDom.render()實現(xiàn)
renderers/shared/stack/reconciler/instantiateReactComponent.js
根據(jù)元素類型創(chuàng)建不同類型的ReactComponents
renderers/shared/stack/reconciler/ReactCompositeComponent.js
ReactComponents根元素的包裝器
調用棧中使用的標簽:
- 函數(shù)調用
= 別名
~ 間接函數(shù)調用
由于不能從 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(),因為從開發(fā)人員的角度來看,它的存在已被JSX掩蓋了。
在編譯時,Babel將JSX中定義的組件轉換為使用適當參數(shù)調用的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;
這是瀏覽器執(zhí)行的真實代碼。 上面的代碼顯示了復合組件App的定義,其中JSX,JavaScript代碼中的HTML標簽穿插語法(例如:<div className=”App”></div>),被轉換為React.createElement()調用。
在應用程序級別,將渲染此組件:
ReactDOM.render(
<App />,
document.getElementById('root')
);
通常由一個名為“ index.js”的JS入口模塊組成。
這個嵌套的組件樹太復雜了,無法成為理想的起點,因此我們現(xiàn)在就忘記了它,而是看一些更簡單的東西-渲染一個簡單的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() - 創(chuàng)建一個 ReactElement
第一步并沒有真正做很多事情。 它只是構造了一個ReactElement實例,其中填充了傳遞給調用堆棧的所有內容。 結果數(shù)據(jù)結構為:

調用棧為:
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可以是多個參數(shù),并且這些參數(shù)將轉移到新分配的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存儲為數(shù)組
}
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" }
},
// 記錄負責創(chuàng)建此元素的組件。
_owner: owner, // scr: --------------> null
};
...
ReactElement@isomorphic/classic/element/ReactElement.js
新構建的ReactElement中填充的字段將由ReactMount.instantiateReactComponent()直接使用,稍后將對其進行詳細說明。 請注意,下一步還將使用ReactElement.createElement()創(chuàng)建一個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),它將在以下呈現(xiàn)步驟中實例化。 (然后將通過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()比看起來要簡單,實際上,該函數(shù)中的大多數(shù)分支(用于UI更新)都被跳過了。 在邏輯進行下一步之前唯一有效的行是:
...
var nextWrappedElement = React.createElement(TopLevelWrapper, {
child: nextElement,
});
...
_renderSubtreeIntoContainer@renderers/dom/client/ReactMount.js
現(xiàn)在,應該很容易看到如何使用React.createElement構造此步驟的目標對象,React.createElement是我們在上一節(jié)中檢查過的函數(shù)。
InstantiateReactComponent()
使用ReactElement [2]創(chuàng)建一個ReactCompositeComponent
此步驟的目的是為頂層組件創(chuàng)建一個基本的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是唯一值得在這里討論的長函數(shù)。 在我們的上下文中,它將檢查ReactElement [2] .type(即TopLevelWrapper)并相應地創(chuàng)建一個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構造函數(shù)的直接調用:
...
// 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
然后真正的構造函數(shù)被調用:
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
我們將在此步驟中創(chuàng)建的對象命名為ReactCompositeComponent [T](頂部為T)。
構造ReactCompositeComponent對象后,下一步是調用batchedMountComponentIntoNode,初始化ReactCompositeComponent [T]并安裝它,這將在下一篇文章中詳細討論。
(原文)Understanding The React Source Code - Initial Rendering (Simple Component) I