【譯】了解React源代碼-初始渲染(簡單組件)1
【譯】了解React源代碼-初始渲染(簡單組件)2
【譯】了解React源代碼-初始渲染(簡單組件)3
【譯】了解React源代碼-初始渲染(類組件)4
【譯】了解React源代碼-初始渲染(類組件)5
【譯】了解React源代碼-UI更新(事務(wù))6
【譯】了解React源代碼-UI更新(事務(wù))7
【譯】了解React源代碼-UI更新(單個(gè)DOM)8
【譯】了解React源代碼-UI更新(DOM樹)9
UI更新本質(zhì)上就是數(shù)據(jù)更改。 React提供了一種直觀,直觀的方式對(duì)前端App進(jìn)行編程,因?yàn)榇蠖鄶?shù)活動(dòng)部件都以狀態(tài)形式收斂,并且大多數(shù)UI任務(wù)都可以通過一個(gè)操作完成

…,我的意思是,只有一個(gè)方法setState()。 在本文中,我們將展開setState()實(shí)現(xiàn),并通過對(duì)單個(gè)DOM元素進(jìn)行變異來窺視diffing算法。
Before we get started, I would like to respond to one common feedback from readers: “why 15.x, why not fiber?”Well, simply put, because synchronous rendering is still alive. Thus, the code base (a.k.a., stack reconciler) specifically designed for synchronous rendering, in my opinion, offers an easier albeit solid ground to establish an initial understanding.
在開始之前,我想回應(yīng)讀者的一個(gè)普遍反饋:“為什么使用15.x,為什么不使用 fiber?”好吧,簡單地說,因?yàn)橥戒秩救匀挥行А?因此,我認(rèn)為,專為同步渲染而設(shè)計(jì)的代碼庫(又稱堆棧協(xié)調(diào)器)為建立初步理解提供了更容易的基礎(chǔ)。
首先,讓我們從{第四篇}擴(kuò)展一個(gè)例子
class App extends Component {
constructor(props) {
super(props);
this.state = {
desc: 'start',
color: 'blue'
};
this.timer = setTimeout(
() => this.tick(),
5000
);
}
tick() {
this.setState({
desc: 'end',
color: 'green'
});
}
render() {
return (
<div className="App">
<div className="App-header">
<img src="main.jpg" className="App-logo" alt="logo" />
<h1> "Welcom to React" </h1>
</div>
<p className="App-intro" style={{color: this.state.color}}>
{ this.state.desc }
</p>
</div>
);
}
}
export default App;
與{第四篇}中使用的App組件相比,新版本在組件構(gòu)建5秒后將style prop添加到<p>節(jié)點(diǎn),并將setState()的desc設(shè)置為“ end”,將顏色設(shè)置為“ green”。
{第四篇}已討論了App的實(shí)例化。
ctl-f “setState”In the same article, I also mentioned ReactInstanceMap, a back link (from the external ReactComponent instance) to the internal ReactCompositeComponent[ins], which will be used very soon.
ctl -f“ setState”在同一篇文章中,我還提到了ReactInstanceMap,這是一個(gè)指向內(nèi)部ReactCompositeComponent [ins]的反向鏈接(從外部ReactComponent實(shí)例)。
在這里,我粘貼數(shù)據(jù)結(jié)構(gòu)作為提醒。

Before transactions
我們從setState()方法主體開始:
ReactComponent.prototype.setState = function (
partialState,
callback
) {
// scr: ---> sanity check
this.updater.enqueueSetState(this, partialState);
if (callback) {
// scr: ---> no callbak
}
};
ReactComponent@isomorphic/modern/class/ReactBaseClasses.js
是的,setState()是從ReactComponent繼承的。
但是等等,this.updater是什么? 它不是在構(gòu)造函數(shù)中設(shè)置為ReactNoopUpdateQueue,并且是no-op空操作嗎? 實(shí)際上,我相信通過對(duì)Transaction(s)和實(shí)例池的了解,您可以從上述的ReactComponent實(shí)例化(第四篇)中追溯,您將能夠很容易地找到this.updater的來源。
I will leave this question open so we can move faster to the core part —virtual DOM and diffing algorithm
我將保留這個(gè)問題,以便我們可以更快地進(jìn)入核心部分-虛擬DOM和差異算法
enqueueSetState: function (publicInstance, partialState) {
// scr: DEV code
// scr: ------------------------------------------------------> 1)
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
if (!internalInstance) {
return;
}
// scr: ------------------------------------------------------> 2)
var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
queue.push(partialState);
// scr: ------------------------------------------------------> 3)
enqueueUpdate(internalInstance);
},
ReactUpdateQueue@renderers/shared/stack/reconciler/ReactUpdateQueue.js
1)這是從反向鏈接ReactInstanceMap獲得內(nèi)部ReactCompositeComponent [ins]的方法;
function getInternalInstanceReadyForUpdate(
publicInstance,
callerName
) {
var internalInstance = ReactInstanceMap.get(publicInstance);
... // scr: DEV code
return internalInstance;
}
getInternalInstanceReadyForUpdate@renderers/shared/stack/reconciler/ReactUpdateQueue.js
2)將一個(gè)數(shù)組(_pendingStateQueue)附加到ReactCompositeComponent [ins],并將更改后的狀態(tài){desc:'end',color:'green'}推入其中;
...
function enqueueUpdate(internalInstance) {
ReactUpdates.enqueueUpdate(internalInstance);
}
...
enqueueUpdate@renderers/shared/stack/reconciler/ReactUpdateQueue.js
到目前為止的調(diào)用棧:
|-ReactComponent.setState()
|-ReactUpdateQueue.enqueueSetState()
|-getInternalInstanceReadyForUpdate()
|-enqueueUpdate()
|-ReactUpdates.enqueueUpdate()
|~~~
在這里,我還將粘貼與交易相關(guān)的調(diào)用圖作為提醒。

In transactions
Transaction(s)完全初始化之后的第一站是
function runBatchedUpdates(transaction) {
var len = transaction.dirtyComponentsLength;
// scr: -----------------------------------> sanity check
...
dirtyComponents.sort(mountOrderComparator);
updateBatchNumber++;
for (var i = 0; i < len; i++) {
var component = dirtyComponents[i];
var callbacks = component._pendingCallbacks;
component._pendingCallbacks = null;
// scr: ------------------------------> logging
...
ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction, updateBatchNumber);
// scr: ------------------------------> logging
if (callbacks) { // scr: -------------> no callbacks
...
}
}
}
ReactUpdates@renderers/shared/stack/reconciler/ReactUpdates.js
這次我們有一個(gè)dirtyComponents, ReactCompositeComponent [ins],它是ReactReconciler.performUpdateIfNecessary()的第一個(gè)參數(shù)。
performUpdateIfNecessary: function (
internalInstance,
transaction,
updateBatchNumber
) {
// scr: DEV code
...
internalInstance.performUpdateIfNecessary(transaction);
// scr: DEV code
...
}
ReactReconciler@renderers/shared/stack/reconciler/ReactUpdates.js
與 ReactReconciler類中的其他大多數(shù)方法一樣,ReactReconciler.performUpdateIfNecessary()將調(diào)用組件的相同方法ReactCompositeComponent.performUpdateIfNecessary()
it’s like a polymorphism in a more explicit way
更明顯地就像是多態(tài)
performUpdateIfNecessary: function (transaction) {
if (this._pendingElement != null) {
// scr: -----------> condition not applied
...
} else if (
this._pendingStateQueue !== null ||
this._pendingForceUpdate
) {
this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);
} else {
// scr: -----------> condition not applied
...
}
},
ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.js
依次調(diào)用ReactCompositeComponent [ins] .updateComponent()。 請(qǐng)注意,_pendingStateQueue是在邏輯進(jìn)入Transaction上下文之前設(shè)置的。
updateComponent: function(
transaction,
prevParentElement,
nextParentElement,
prevUnmaskedContext,
nextUnmaskedContext,
) {
var inst = this._instance; // scr: ---------------------------> 1)
// scr: sanity check and code that is not applicable this time
...
// scr: ------------------------------------------------------> 2)
var nextState = this._processPendingState(nextProps, nextContext);
var shouldUpdate = true;
if (!this._pendingForceUpdate) {
if (inst.shouldComponentUpdate) { // scr: ------------------> 3)
shouldUpdate = inst.shouldComponentUpdate(
nextProps,
nextState,
nextContext,
);
} else {
if (this._compositeType === CompositeTypes.PureClass) {
// scr: ---------------> it is ImpureClass, not applicable
...
}
}
}
this._updateBatchNumber = null;
if (shouldUpdate) {
this._pendingForceUpdate = false;
// Will set `this.props`, `this.state` and `this.context`.
this._performComponentUpdate( // scr: --------------------> 4)
nextParentElement,
nextProps,
nextState,
nextContext,
transaction,
nextUnmaskedContext,
);
} else {
// scr: code that is not applicable this time
...
}
},
ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.js
1)從ReactCompositeComponent [ins] ._ instance {Figure-I}獲得外部ReactComponent實(shí)例(App);
2)使用Object.assign()合并ReactCompositeComponent [ins] ._ pendingStateQueue({desc:'end',color:'green'})中的部分狀態(tài)和現(xiàn)有狀態(tài);
_processPendingState: function(props, context) {
// scr: -------> obtain the App {Figure-I}
var inst = this._instance;
var queue = this._pendingStateQueue;
// scr: code that is not applicable this time
...
var nextState = Object.assign({}, replace ? queue[0] : inst.state);
for (var i = replace ? 1 : 0; i < queue.length; i++) {
var partial = queue[i];
Object.assign(
nextState,
typeof partial === 'function'
? partial.call(inst, nextState, props, context)
: partial,
);
}
return nextState;
},
ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.js
3)這是提供給開發(fā)人員的生命周期函數(shù),以避免在setState()不更改關(guān)鍵狀態(tài)的情況下執(zhí)行協(xié)調(diào)(以下處理邏輯);
Most likely you do not need this function
最有可能您不需要此功能
4)進(jìn)入下一站。
_performComponentUpdate: function(
nextElement,
nextProps,
nextState,
nextContext,
transaction,
unmaskedContext,
) {
var inst = this._instance; // scr: {Figure-I}
// scr: code that is not applicable this time
...
// scr: invoke App's life cycle method if defined
if (inst.componentWillUpdate) {
inst.componentWillUpdate(nextProps, nextState, nextContext);
}
// scr: code that is not applicable this time
...
inst.state = nextState;
...
this._updateRenderedComponent(transaction, unmaskedContext);
// scr: queue App's life cycle method if defined
if (hasComponentDidUpdate) {
...
}
},
ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.js
只需將App的state設(shè)置為新合并的狀態(tài)即可。 并調(diào)用this._updateRenderedComponent(),它是差異算法的入口點(diǎn)。
到目前為止的調(diào)用棧:
...
|~~~
|-runBatchedUpdates()
|-performUpdateIfNecessary()
|-ReactCompositeComponent[ins].performUpdateIfNecessary()
|-this.updateComponent()
|-this._processPendingState()
|-this._performComponentUpdate() ___
|-this._updateRenderedComponent() |
... diffing
然后,邏輯處理到差分算法。
Virtual DOM
在開始研究Diffing算法之前,最好先對(duì)虛擬DOM的確切含義達(dá)成共識(shí),因?yàn)樵撔g(shù)語未出現(xiàn)在代碼庫中。
在這里,我貼上{第五篇}中的圖片作為提醒

·ReactElement·是我們將要達(dá)成共識(shí)的虛擬DOM。 {第四篇,第五篇}還討論了虛擬DOM樹的最初建立方式。
In MVC terms
ReactElementsare modals which contain only data. On the other hand,ReactDOMComponentsare controllers that offer actionable methods.
用MVC術(shù)語來說,React Elements是僅包含數(shù)據(jù)的模態(tài)。 另一方面,ReactDOMComponent是提供可操作方法的控制器。
Diffing
上圖給出了在{第四篇}中生成的舊的虛擬DOM樹。
ctl-f “in _renderValidatedComponent()”
此步驟將基于更改后的狀態(tài),使用ReactCompositeComponent [ins] ._ renderValidatedComponent()生成一個(gè)新對(duì)象,以進(jìn)行區(qū)分。
_updateRenderedComponent: function (transaction, context) {
var prevComponentInstance = this._renderedComponent; // scr: -> 1)
// scr: ------------------------------------------------------> 2)
var prevRenderedElement = prevComponentInstance._currentElement;
// scr: create a new DOM tree
var nextRenderedElement = this._renderValidatedComponent();
var debugID = 0;
// scr: DEV code
...
if (shouldUpdateReactComponent( // scr: ----------------------> 3)
prevRenderedElement,
nextRenderedElement)
) {
ReactReconciler.receiveComponent( // scr: ------------------> 5)
prevComponentInstance,
nextRenderedElement,
transaction,
this._processChildContext(context)
);
} else { // scr: ---------------------------------------------> 4)
// scr: code that is not applicable this time
...
}
},
ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.js
1)通過ReactCompositeComponent [ins] {Figure-I}獲得ReactDOMComponent [6];
2)在App [ins] .render()中級(jí)聯(lián)調(diào)用React.createElement()來創(chuàng)建新的DOM樹{第四篇},其中唯一不同的DOM節(jié)點(diǎn)是:

3)diffing算法的第一個(gè)比較是新舊根元素的類型之間的比較;
function shouldUpdateReactComponent(prevElement, nextElement) {
var prevEmpty = prevElement === null || prevElement === false;
var nextEmpty = nextElement === null || nextElement === false;
if (prevEmpty || nextEmpty) {
return prevEmpty === nextEmpty;
}
var prevType = typeof prevElement;
var nextType = typeof nextElement;
if (prevType === 'string' || prevType === 'number') {
return nextType === 'string' || nextType === 'number';
} else {
return nextType === 'object' && prevElement.type === nextElement.type && prevElement.key === nextElement.key;
}
}
shouldUpdateReactComponent@renderers/shared/shared/shouldUpdateReactComponent.js
4)如果它們不相同,則從頭開始構(gòu)建新樹-組件安裝過程與{第五篇}中討論的過程類似;
whenever the root elements have different types, React will tear down the old tree and build the new tree from scratch
每當(dāng)根元素具有不同類型時(shí),React都會(huì)拆開舊樹并從頭開始構(gòu)建新樹
5)如果相同,則開始DOM更新過程。
receiveComponent: function (nextElement, transaction, context) {
var prevElement = this._currentElement;
this._currentElement = nextElement;
this.updateComponent(transaction,
prevElement,
nextElement,
context);
},
updateComponent: function(
transaction,
prevElement,
nextElement,
context
) {
var lastProps = prevElement.props;
var nextProps = this._currentElement.props;
// scr: code that is not applicable this time
...
// scr: ------------------------------------------------------> 1)
this._updateDOMProperties(lastProps, nextProps, transaction);
// scr: ------------------------------------------------------> 2)
this._updateDOMChildren(lastProps, nextProps, transaction, context);
// scr: code that is not applicable this time
...
},
ReactDOMComponent@renderers/dom/shared/ReactDOMComponent.js
1)從舊的虛擬DOM(lastProps)和新創(chuàng)建的虛擬DOM(nextProps)獲取props;
2)ReactDOMComponent._updateDOMProperties()檢查DOM道具的舊版本和新版本,并調(diào)用CSSPropertyOperations.setValueForStyles()來更新DOM(如果不同);
3)ReactDOMComponent._updateDOMChildren()檢查DOM內(nèi)容(文本,內(nèi)部HTML)的舊版本和新版本,并調(diào)用ReactDOMComponent.updateTextContent()來更新DOM(文本)的內(nèi)容。
靜態(tài)調(diào)用棧
... ___
ReactReconciler.receiveComponent() <----------------| |
|-ReactDOMComponent.receiveComponent() | |
|-this.updateComponent() | |
|-this._updateDOMProperties() | diffing
|-CSSPropertyOperations.setValueForStyles() | |
|-this._updateDOMChildren() | |
|-this.updateTextContent() | |
|-recursing children (not the focus this time) --| |
---
通過觀察靜態(tài)調(diào)用堆棧,不難推斷出遞歸的工作方式。
1)此遞歸的一次迭代更新了一個(gè)虛擬DOM的屬性;
2)ReactDOMComponent.updateDOMChildren()還負(fù)責(zé)遍歷當(dāng)前虛擬DOM的直接子對(duì)象,并為每個(gè)子對(duì)象調(diào)用下一個(gè)迭代。
note that sub DOM recursing is not the focus of this post
請(qǐng)注意,sub DOM遞歸不是本文的重點(diǎn)
我在上面的調(diào)用堆棧中折疊了一些方法調(diào)用,
|-ReactReconciler.receiveComponent()
|-ReactDOMComponent[n].receiveComponent()
|-this.updateComponent()
=>
|-ReactDOMComponent[n].updateComponent()
并清晰地調(diào)用調(diào)用棧:
...
|-ReactDOMComponent[6].updateComponent()
|-this._updateDOMProperties() // scr: ----> same
|-this._updateDOMChildren
|-recursing children (not the focus this time...)
|-ReactDOMComponent[4].updateComponent()
|-this._updateDOMProperties() // scr: ----> same
|-this._updateDOMChildren
|-recursing children (not the focus this time...)
|-ReactDOMComponent[2].updateComponent()
|-this._updateDOMProperties() // scr: ----> same
|-this._updateDOMChildren // scr: ----> same
|-ReactDOMComponent[3].updateComponent()
|-this._updateDOMProperties() // scr: ----> same
|-this._updateDOMChildren // scr: ----> same
|-ReactDOMComponent[5].updateComponent()
|-this._updateDOMProperties()
|-CSSPropertyOperations.setValueForStyles()
|-this._updateDOMChildren
|-this.updateTextContent()
ReactDOMComponent._updateDOMProperties()—檢查DOM是否已更改
This is the overlooked method in {post three *6}In this article we focus on only STYLE updating related code.
這是{第三篇 * 6}中被忽略的方法。在本文中,我們僅關(guān)注STYLE更新相關(guān)代碼。
_updateDOMProperties: function(lastProps, nextProps, transaction) {
var propKey;
var styleName;
var styleUpdates;
// scr: --------------------------------------------------------> 1)
for (propKey in lastProps) {
if (
nextProps.hasOwnProperty(propKey) ||
!lastProps.hasOwnProperty(propKey) ||
lastProps[propKey] == null
) {
continue;
}
if (propKey === STYLE) {
var lastStyle = this._previousStyleCopy;
for (styleName in lastStyle) {
if (lastStyle.hasOwnProperty(styleName)) {
styleUpdates = styleUpdates || {};
styleUpdates[styleName] = '';
}
}
this._previousStyleCopy = null;
} else if ... {
// scr: not the focus this time
...
}
}
// scr: --------------------------------------------------> end 1)
for (propKey in nextProps) {
var nextProp = nextProps[propKey];
var lastProp = propKey === STYLE
? this._previousStyleCopy
: lastProps != null ? lastProps[propKey] : undefined;
if (
!nextProps.hasOwnProperty(propKey) ||
nextProp === lastProp ||
(nextProp == null && lastProp == null)
) {
continue;
}
if (propKey === STYLE) {
if (nextProp) {
// scr: DEV code
...
// scr: -------------------------------------------------> 2)
nextProp = this._previousStyleCopy = Object.assign({}, nextProp);
} else {
this._previousStyleCopy = null;
}
if (lastProp) { // scr: ----------------------------------> 3)
// scr: the comment applies here -----------------------> a)
// Unset styles on `lastProp` but not on `nextProp`.
for (styleName in lastProp) {
if (
lastProp.hasOwnProperty(styleName) &&
(!nextProp || !nextProp.hasOwnProperty(styleName))
) {
styleUpdates = styleUpdates || {};
styleUpdates[styleName] = '';
}
}
// scr: the comment applies here -----------------------> b)
// Update styles that changed since `lastProp`.
for (styleName in nextProp) {
if (
nextProp.hasOwnProperty(styleName) &&
lastProp[styleName] !== nextProp[styleName]
) {
styleUpdates = styleUpdates || {};
styleUpdates[styleName] = nextProp[styleName];
}
}
} else { // scr: -----------------------------------------> 4)
// Relies on `updateStylesByID` not mutating `styleUpdates`.
styleUpdates = nextProp;
}
} else if (...) {
// scr: DEV code
...
}
}
if (styleUpdates) { // scr: ----------------------------------> 5)
CSSPropertyOperations.setValueForStyles(
getNode(this),
styleUpdates,
this,
);
}
},
ReactDOMComponent@renderers/dom/shared/ReactDOMComponent.js
1)如果新屬性根本不包含“style”,
...
if (nextProps.hasOwnProperty(propKey) ||...) {
continue;
} // scr: else, do something
...
將所有現(xiàn)有樣式條目標(biāo)記為“刪除”,請(qǐng)注意,現(xiàn)有樣式存儲(chǔ)在this._previousStyleCopy中,步驟2)中;
2)將nextProp(當(dāng)前樣式)復(fù)制到this._previousStyleCopy;
3)如果已有樣式,
var lastProp = propKey === STYLE
? this._previousStyleCopy
...
if (lastProp) {
...
請(qǐng)通過以下方式進(jìn)行更新:a)將nextProp中沒有的現(xiàn)有樣式條目標(biāo)記為“刪除”,b)nextProp中的樣式條目與相同鍵上的現(xiàn)有條目不同,將它們標(biāo)記為“添加”;
4)如果沒有,只需將nextProp中的所有樣式標(biāo)記為“添加”;
5)進(jìn)行真正的DOM操作。 請(qǐng)注意,getNode()是ReactDOMComponentTree.getNodeFromInstance()的別名,該別名使用ReactDOMComponent._hostNode來獲取關(guān)聯(lián)的DOM元素{Figure-III} {第三篇}。
ctl-f “ReactDOMComponent[ins]._hostNode”
CSSPropertyOperations.setValueForStyles()-更新屬性
setValueForStyles: function(node, styles, component) {
var style = node.style;
for (var styleName in styles) {
if (!styles.hasOwnProperty(styleName)) {
continue;
}
// scr: DEV code or code that is not applicable
...
if (isCustomProperty) {
...
} else if (styleValue) {
style[styleName] = styleValue;
} else {
code that is not applicable this time
...
}
}
},
CSSPropertyOperations@renderers/dom/shared/CSSPropertyOperations.js
這里唯一適用的行是style [styleName] = styleValue; 使用前面方法中標(biāo)記的style設(shè)置node.style。
結(jié)果,Node.style ['color'] ='red'。
_updateDOMChildren —檢查DOM的內(nèi)容是否已更改(并遞歸其子級(jí))
We omit the
dangerouslySetInnerHTMLrelated code and focus only on hot paths
我們省略了與DangerouslySetInnerHTML相關(guān)的代碼,僅關(guān)注熱門路徑
_updateDOMChildren: function(
lastProps,
nextProps,
transaction,
context
) {
var lastContent = CONTENT_TYPES[typeof lastProps.children]
? lastProps.children
: null;
var nextContent = CONTENT_TYPES[typeof nextProps.children]
? nextProps.children
: null;
// scr: code that is not applicable
...
// Note the use of `!=` which checks for null or undefined.
// scr: used by recursing children, to be continued...
var lastChildren = lastContent != null ? null : lastProps.children;
var nextChildren = nextContent != null ? null : nextProps.children;
// scr: code that is not applicable
...
if (lastChildren != null && nextChildren == null) {
// scr: recursing children, to be continued...
this.updateChildren(null, transaction, context);
} else if (lastHasContentOrHtml && !nextHasContentOrHtml) {
// scr: DEV code and code that is not applicable
...
}
if (nextContent != null) {
if (lastContent !== nextContent) {
this.updateTextContent('' + nextContent);
// scr: DEV code
...
}
} else if (nextHtml != null) {
// scr: code that is not applicable
...
} else if (nextChildren != null) {
// scr: DEV code
...
// scr: recursing children, to be continued...
this.updateChildren(nextChildren, transaction, context);
}
},
ReactDOMComponent@renderers/dom/shared/ReactDOMComponent.js
這里唯一適用的行是
this.updateTextContent(‘’ + nextContent);
ReactDOMComponent.updateTextContent()-更新內(nèi)容
大概ReactDOMComponent.updateTextContent()用于將文本從“start”設(shè)置為“end”。 但是這個(gè)過程的調(diào)用棧對(duì)于這個(gè)簡單的操作來說太深了,
updateTextContent: function(nextContent) {
var prevChildren = this._renderedChildren;
// Remove any rendered children. scr: -------> the comment applies
ReactChildReconciler.unmountChildren(prevChildren, false);
for (var name in prevChildren) {
// scr: sanity check
...
}
// Set new text content. scr: ---------------> the comment applies
var updates = [makeTextContent(nextContent)];
processQueue(this, updates);
},
function processQueue(inst, updateQueue) {
ReactComponentEnvironment.processChildrenUpdates(inst, updateQueue);
}
ReactMultiChild@renderers/shared/stack/reconciler/ReactMultiChild.js
在這里,ReactComponentBrowserEnvironment作為ReactComponentEnvironment被注入。
dangerouslyProcessChildrenUpdates: function(parentInst, updates) {
var node = ReactDOMComponentTree.getNodeFromInstance(parentInst);
DOMChildrenOperations.processUpdates(node, updates);
},
ReactDOMIDOperations@renderers/dom/client/ReactDOMIDOperations.js
The ReactDOMComponentTree.getNodeFromInstance() method is discussed in the previous section.
上一節(jié)討論了ReactDOMComponentTree.getNodeFromInstance()方法。
processUpdates: function(parentNode, updates) {
// scr: DEV code
...
for (var k = 0; k < updates.length; k++) {
var update = updates[k];
switch (update.type) {
// scr: code that is not applicable
...
case 'TEXT_CONTENT':
setTextContent(parentNode, update.content);
// scr: DEV code
...
break;
...
DOMChildrenOperations@renderers/dom/client/utils/DOMChildrenOperations.js
不出所料,此堆棧中的最后一張卡是setTextContent(),它直接設(shè)置Node.textContent。 {第五篇}中介紹了此方法,因此我將不再重復(fù)其實(shí)現(xiàn)。
ReactDOMComponent.updateTextContent()的子調(diào)用堆棧及其“end”結(jié)果:
|-ReactDOMComponent.updateTextContent()
|-processQueue()
|-ReactComponentEnvironment.processChildrenUpdates()
|=ReactDOMIDOperations.dangerouslyProcessChildrenUpdates()
|-ReactDOMComponentTree.getNodeFromInstance()
|-DOMChildrenOperations.processUpdates()
|-setTextContent()
|-Node.textContent = 'end'
在下一篇文章中,我們將通過觀察DOM樹的突變來進(jìn)一步研究差異算法,這也將結(jié)束本系列文章(一段時(shí)間)。 我希望您下次使用setState()時(shí)會(huì)感到更多。

(原文鏈接)Understanding The React Source Code - UI Updating (Individual DOM) VIII