原文鏈接地址:https://github.com/Nealyang 轉(zhuǎn)載請(qǐng)注明出處
前言
戰(zhàn)戰(zhàn)兢兢寫下開篇...也感謝小蘑菇大神以及網(wǎng)上各路大神的博客資料參考~
閱讀源碼的方式有很多種信认,廣度優(yōu)先法卸例、調(diào)用棧調(diào)試法等等产舞,此系列文章道伟,采用基線法役拴,顧名思義,就是以低版本為基線,逐漸了解源碼的演進(jìn)過程和思路。
react最初的設(shè)計(jì)靈感來源于游戲渲染的機(jī)制:當(dāng)數(shù)據(jù)變化時(shí)浇坐,界面僅僅更新變化的部分而形成新的一幀渲染。所以設(shè)計(jì)react的核心就是認(rèn)為UI只是把數(shù)據(jù)通過映射關(guān)系變換成另一種形式的數(shù)據(jù)黔宛,也就是展示方式近刘。傳統(tǒng)上,web架構(gòu)使用模板或者HTML指令構(gòu)造頁面臀晃。react則處理構(gòu)建用戶界面通過將他們份極為virtual dom觉渴,當(dāng)然這也是react的核心,整個(gè)react架構(gòu)的設(shè)計(jì)理念也是為此展開的徽惋。
準(zhǔn)備工作
我們采用基線法去學(xué)習(xí)react源碼案淋,所以目前基于的版本為stable-0.3,后面我們在逐步分析學(xué)習(xí)演變的版本。
clone代碼
git clone https://github.com/facebook/react.git
git checkout 0.3-stable
React源代碼都在src目錄中险绘,src包含了8個(gè)目錄踢京,其主要內(nèi)容描述見下表回右。
目 錄 | 內(nèi)容 |
---|---|
core | React 核心類 |
domUtil | Dom操作和CSS操作的相關(guān)工具類 |
environment | 當(dāng)前JS執(zhí)行環(huán)境的基本信息 |
event | React事件機(jī)制的核心類 |
eventPlugins | React事件機(jī)制的事件綁定插件類 |
test | 測試目錄 |
utils | 各種工具類 |
vendor | 可替換模塊存放目錄 |
我們將該版本編譯后的代碼放到example下,引入到basic/index.html中運(yùn)行調(diào)試漱挚。
組件初始化
使用
這里還是以basic.html中的代碼為例
<script>
var ExampleApplication = React.createClass({
render: function() {
var elapsed = Math.round(this.props.elapsed / 100);
var seconds = elapsed / 10 + (elapsed % 10 ? '' : '.0' );
var message =
'React has been successfully running for ' + seconds + ' seconds.';
return React.DOM.p(null, message);
}
});
var start = new Date().getTime();
setInterval(function() {
React.renderComponent(
ExampleApplication({elapsed: new Date().getTime() - start}),
document.getElementById('container')
);
}, 50);
</script>
回到我們說的組件初始化,抽離下上面的代碼就是:
var ExampleApplication = React.createClass({render:function(){ return <div>Nealyang</div> }})
熟悉react使用的人都知道渺氧,render方法不能為空旨涝,當(dāng)然,createClass中我們也可以去寫一寫生命周期的鉤子函數(shù)侣背,這里我們暫且省略白华,畢竟目前我們更加的關(guān)注react組建的初始化過程。
同樣贩耐,熟悉react使用方法的人也會(huì)有疑惑了弧腥,怎么實(shí)例代碼中的render最后return的是React.DOM.p(null,message)
所以到這里,就不得不說一下react的編譯階段了
編譯階段
我們都知道潮太,在js中直接編寫html代碼管搪,或者。铡买。更鲁。jsx語法這樣的AST,在js詞法分析階段就會(huì)拋出異常的奇钞。
對(duì)的澡为,所以我們在編寫react代碼的時(shí)候都會(huì)借助babel去轉(zhuǎn)碼
從babel官網(wǎng)上寫個(gè)例子即可看出:
對(duì)呀!明明人家用的是react.createElement方法景埃,我們怎么出現(xiàn)個(gè)React.DOM.p...
OK媒至,歷史原因:
- react現(xiàn)在版本中,使用babel-preset-react來編譯jsx谷徙,這個(gè)preset又包含了4個(gè)插件拒啰,其中transform-react-jsx負(fù)責(zé)編譯jsx,調(diào)用了React.createElement函數(shù)生成虛擬組件
- 在react-0.3里蒂胞,編譯結(jié)果稍稍有些不同图呢,官方給出的示例文件,使用JSXTransformer.js編譯jsx(也就是
<script src="../source/JSXTransformer.js"></script>
)骗随,對(duì)于native組件和composite組件編譯的方式也不一致蛤织。也就是我們看到的React.DOM.p or ReactComponsiteComponent- native組件:編譯成React.DOM.xxx(xxx如div),函數(shù)運(yùn)行返回一個(gè)ReactNativeComponent實(shí)例鸿染。
- composite組件:編譯成createClass返回的函數(shù)調(diào)用指蚜,函數(shù)運(yùn)行返回一個(gè)ReactCompositeComponent實(shí)例
題外話,不管用什么框架涨椒,到瀏覽器這部分的摊鸡,什么花里胡哨的都不復(fù)存在绽媒。我這就是js、css免猾、html是辕。所以我們這里的ReactCompositeComponent最終其實(shí)還是需要轉(zhuǎn)成原生元素的 。\
組件創(chuàng)建
從React.js中我們可以找到createClass的出處:
"use strict";
var ReactCompositeComponent = require('ReactCompositeComponent');
...
var React = {
...
createClass: ReactCompositeComponent.createClass,
...
};
module.exports = React;
- createClass 代碼
var ReactCompositeComponentBase = function() {};
function mixSpecIntoComponent(Constructor, spec) {
var proto = Constructor.prototype;
for (var name in spec) {
if (!spec.hasOwnProperty(name)) {
continue;
}
var property = spec[name];
var specPolicy = ReactCompositeComponentInterface[name];
if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {
RESERVED_SPEC_KEYS[name](Constructor, property);
} else if (property && property.__reactAutoBind) {
if (!proto.__reactAutoBindMap) {
proto.__reactAutoBindMap = {};
}
proto.__reactAutoBindMap[name] = property.__reactAutoBind;
} else if (proto.hasOwnProperty(name)) {
// For methods which are defined more than once, call the existing methods
// before calling the new property.
proto[name] = createChainedFunction(proto[name], property);
} else {
proto[name] = property;
}
}
}
createClass: function (spec) {
var Constructor = function (initialProps, children) {
this.construct(initialProps, children);
};
// ReactCompositeComponentBase是React復(fù)合組件的原型函數(shù)
Constructor.prototype = new ReactCompositeComponentBase();
Constructor.prototype.constructor = Constructor;
// 把消費(fèi)者聲明配置spec合并到Constructor.prototype中
mixSpecIntoComponent(Constructor, spec);
// 判斷合并后的結(jié)果有沒有render猎提,如果沒有 render获三,拋出一個(gè)異常
invariant(
Constructor.prototype.render,
'createClass(...): Class specification must implement a `render` method.'
);
//工廠
var ConvenienceConstructor = function (props, children) {
return new Constructor(props, children);
};
ConvenienceConstructor.componentConstructor = Constructor;
ConvenienceConstructor.originalSpec = spec;
return ConvenienceConstructor;
},
- mixSpecIntoComponent 方法就是講spec的屬性賦值給Constructor的原型上
- createClass返回一個(gè)ConvenienceConstructor構(gòu)造函數(shù),構(gòu)造函數(shù)接受props锨苏、children 構(gòu)造函數(shù)的靜態(tài)方法componentConstructor和originalSpec分別指向Constructor和spec疙教。
- 有種類似于寄生組合式繼承的寫法,Constructor為每一個(gè)組件實(shí)例的原型(
var instance = new Constructor(); instance.construct.apply(instance, arguments);
)伞租。Constructor原型指向ReactCompositeComponentBase贞谓,又把構(gòu)造器指向Constructor自己。然后把傳入的spec合并到Constructor.prototype中葵诈。判斷合并后的結(jié)果有沒有render裸弦,如果沒有 render,拋出一個(gè)異常
其實(shí)很多人看到這估計(jì)都會(huì)很疑惑作喘,為毛這樣搞烁兰??徊都?直接返回個(gè)構(gòu)造函數(shù)不就可以了嘛沪斟。
其實(shí)react在后面做diff算法的時(shí)候,是采用組件的Constructor來判斷組件是否相同的暇矫。如此可以保證每個(gè)createClass創(chuàng)建出來的組件都是一個(gè)新的Constructor主之。
ok,那么我直接用寄生繼承呀
// 寫法1
const createClass = function(spec) {
var Constructor = function (initialProps, children) {
this.construct(initialProps, children);
};
Constructor.prototype = new ReactCompositeComponentBase();
Constructor.prototype.constructor = Constructor;
mixSpecIntoComponent(ReactCompositeComponentBase, spec)
return Constructor
}
const Table1 = new createClass(spec)(props, children);
//console.log(Table1.constructor)
為什么還需要ConvenienceConstructor呢李根?說實(shí)話槽奕,我也不知道,然后看了在網(wǎng)上查到相關(guān)信息說道:
上面寫法在大多數(shù)情況下并不會(huì)產(chǎn)生什么問題房轿,但是粤攒,當(dāng)團(tuán)隊(duì)里的人無意中修改錯(cuò)點(diǎn)什么,比如:
Table1.prototype.onClick = null
這樣囱持,所有Table1實(shí)例化的組件夯接,onClick全部為修改后的空值
<Table1 />
<Table1 />
我們知道,js是動(dòng)態(tài)解釋型語言纷妆,函數(shù)可以運(yùn)行時(shí)被隨意篡改盔几。而靜態(tài)編譯語言在運(yùn)行時(shí)期間,函數(shù)不可修改(某些靜態(tài)語言也可以修改)掩幢。所以采用這種方式防御用戶對(duì)代碼的篡改逊拍。
組件實(shí)例化
既然createClass返回的是一個(gè)構(gòu)造函數(shù)上鞠,那么我們就來看看他的實(shí)例化吧
/**
* Base constructor for all React component.
*
* Subclasses that override this method should make sure to invoke
* `ReactComponent.Mixin.construct.call(this, ...)`.
*
* @param {?object} initialProps
* @param {*} children
* @internal
*/
construct: function (initialProps, children) {
this.props = initialProps || {};
if (typeof children !== 'undefined') {
this.props.children = children;
}
// Record the component responsible for creating this component.
this.props[OWNER] = ReactCurrentOwner.current;
// All components start unmounted.
this._lifeCycleState = ComponentLifeCycle.UNMOUNTED;
},
其實(shí)也就是將props、children掛載到this.props上 以及生命周期的設(shè)置芯丧。這里暫且不說芍阎,因?yàn)槲乙舱诳础SШ恪D茉M圻沁?/p>
這里的
this.props[OWNER] = ReactCurrentOwner.current;
this.props[OWNER]指的是當(dāng)前組件的容器(父)組件實(shí)例
如果我們直接在basic.html中打印就直接出來的是null,但是如果像如下的方式書寫:
const Children = React.createClass({
componentDidMount = () => console.log(this.props["{owner}"]),
render = () => null
})
const Parent = React.createClass({
render: () => <Children />
})
這里輸出的就是Parent組件實(shí)例肿轨。
再看看ReactCurrentOwner.current的賦值就明白了
_renderValidatedComponent: function () {
ReactCurrentOwner.current = this;
var renderedComponent = this.render();
ReactCurrentOwner.current = null;
invariant(
ReactComponent.isValidComponent(renderedComponent),
'%s.render(): A valid ReactComponent must be returned.',
this.constructor.displayName || 'ReactCompositeComponent'
);
return renderedComponent;
}
可以看出來,在執(zhí)行render前后蕊程,分別設(shè)置了ReactCurrentOwner.current的值椒袍,這樣就能保證render函數(shù)內(nèi)的子組件能賦上當(dāng)前組件的實(shí)例,也就是this藻茂。
組件渲染
我們先撇開事務(wù)驹暑、事件池、生命周期辨赐、diff當(dāng)然也包括fiber 等优俘,先不談,其實(shí)渲染就是將經(jīng)過babel編譯后的掀序,當(dāng)然這里是JSXTransformer.js編譯后的Ojb給寫入到HTML中而已帆焕。
export default function render(vnode, parent) {
let dom;
if (typeof vnode === 'string') {
dom = document.createTextNode(vnode);
// let span_dom = document.createElement('span')
// span_dom.appendChild(dom);
// parent.appendChild(span_dom);
parent.appendChild(dom);
} else if (typeof vnode.nodeName === 'string') {
dom = document.createElement(vnode.nodeName);
setAttrs(dom, vnode.props);
parent.appendChild(dom)
for(let i = 0; i < vnode.children.length; i++) {
render(vnode.children[i], dom)
}
}else if(typeof vnode.nodeName === 'function'){
let innerVnode = vnode.nodeName.prototype.render();
render(innerVnode,parent)
}
}
function setAttrs(dom, props) {
const ALL_KEYS = Object.keys(props);
ALL_KEYS.forEach(k =>{
const v = props[k];
// className
if(k === 'className'){
dom.setAttribute('class',v);
return;
}
if(k == "style") {
if(typeof v == "string") {
dom.style.cssText = v
}
if(typeof v == "object") {
for (let i in v) {
dom.style[i] = v[i]
}
}
return
}
if(k[0] == "o" && k[1] == "n") {
const capture = (k.indexOf("Capture") != -1)
dom.addEventListener(k.substring(2).toLowerCase(),v,capture)
return
}
dom.setAttribute(k, v)
})
}
是的,就這樣
OK不恭,回到源碼~
在我們目前使用的react版本中叶雹,渲染調(diào)用的是ReactDOM.render方法,這里ReactMount.renderComponent為我們的入口方法换吧。
ReactMount.renderComponent在react初探章節(jié)講過折晦。如果組件渲染過,就更新組件屬性沾瓦,如果組件沒有渲染過满着,掛載組件事件,并把虛擬組件渲染成真實(shí)組件插入container內(nèi)贯莺。通常风喇,我們很少去調(diào)用兩次renderComponent,所以大多數(shù)情況下不會(huì)更新組件屬性而是新創(chuàng)建dom節(jié)點(diǎn)并插入到container中缕探。
ReactComponent.mountComponentIntoNode之內(nèi)開啟了一個(gè)事務(wù)响驴,事務(wù)保證渲染階段不會(huì)有任何事件觸發(fā),并阻斷的componentDidMount事件撕蔼,待執(zhí)行后執(zhí)行等豁鲤,事務(wù)在功能一章我們會(huì)詳細(xì)講解秽誊,這里不細(xì)討論。
ReactComponent._mountComponentIntoNode這個(gè)函數(shù)調(diào)用mountComponent獲得要渲染的innerHTML琳骡,然后更新container的innerHTML锅论。
ReactCompositeComponent.mountComponent是最主要的邏輯方法。這個(gè)函數(shù)內(nèi)處理了react的生命周期以及componentWillComponent和componentDidMount生命周期鉤子函數(shù)楣号,調(diào)用render返回實(shí)際要渲染的內(nèi)容最易,如果內(nèi)容是復(fù)合組件,仍然會(huì)調(diào)用mountComponent炫狱,復(fù)合組件最終一定會(huì)返回原生組件藻懒, 并且最終調(diào)用ReactNativeComponent的mountComponent函數(shù)生成要渲染的innerHTML。
renderComponent: function(nextComponent, container) {
var prevComponent = instanceByReactRootID[getReactRootID(container)];
if (prevComponent) {
var nextProps = nextComponent.props;
ReactMount.scrollMonitor(container, function() {
prevComponent.replaceProps(nextProps);
});
return prevComponent;
}
ReactMount.prepareTopLevelEvents(ReactEventTopLevelCallback);
var reactRootID = ReactMount.registerContainer(container);
instanceByReactRootID[reactRootID] = nextComponent;
nextComponent.mountComponentIntoNode(reactRootID, container);
return nextComponent;
},
這段代碼邏輯大概就是上面的流程圖视译,這里不再贅述嬉荆。
-
mountComponentIntoNode
從debugger中,可以看出mountComponentIntoNode第一個(gè)參數(shù)其實(shí)傳入的是react分配給組件的一個(gè)唯一標(biāo)識(shí)
mountComponentIntoNode: function(rootID, container) {
var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
transaction.perform(
this._mountComponentIntoNode,
this,
rootID,
container,
transaction
);
ReactComponent.ReactReconcileTransaction.release(transaction);
},
源碼中酷含,這里跟事務(wù)扯到了關(guān)系鄙早,其實(shí)我們只要關(guān)注渲染本身,所以這里我們直接看this._mountComponentIntoNode的方法實(shí)現(xiàn)
- _mountComponentIntoNode
_mountComponentIntoNode: function(rootID, container, transaction) {
var renderStart = Date.now();
var markup = this.mountComponent(rootID, transaction);
ReactMount.totalInstantiationTime += (Date.now() - renderStart);
var injectionStart = Date.now();
// Asynchronously inject markup by ensuring that the container is not in
// the document when settings its `innerHTML`.
var parent = container.parentNode;
if (parent) {
var next = container.nextSibling;
parent.removeChild(container);
container.innerHTML = markup;
if (next) {
parent.insertBefore(container, next);
} else {
parent.appendChild(container);
}
} else {
container.innerHTML = markup;
}
ReactMount.totalInjectionTime += (Date.now() - injectionStart);
},
上述代碼流程大概如下:
流程的確如上椅亚,作為一個(gè)初探源碼者限番,我當(dāng)然不關(guān)心你到底是在哪innerHTML的,我想知道你是腫么把jsx編譯后的Obj轉(zhuǎn)成HTML的哇~
- ReactCompositeComponent.mountComponent
這里類變成了ReactCompositeComponent(debugger可以跟蹤每一個(gè)函數(shù))
源碼中的this.mountComponent呀舔,為什么不是調(diào)用ReactComponent.mountComponent呢弥虐?這里主要使用了多重繼承機(jī)制(Mixin,后續(xù)講解)媚赖。
mountComponent: function(rootID, transaction) {
// 掛在組件ref(等于當(dāng)前組件實(shí)例)到this.refs上
ReactComponent.Mixin.mountComponent.call(this, rootID, transaction);
// Unset `this._lifeCycleState` until after this method is finished.
// 這是生命周期
this._lifeCycleState = ReactComponent.LifeCycle.UNMOUNTED;
this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;
// 組件聲明有props躯舔,執(zhí)行校驗(yàn)
if (this.constructor.propDeclarations) {
this._assertValidProps(this.props);
}
// 為組件聲明時(shí)間綁定this
if (this.__reactAutoBindMap) {
this._bindAutoBindMethods();
}
//獲取state
this.state = this.getInitialState ? this.getInitialState() : null;
this._pendingState = null;
// 如果組件聲明componentWillMount函數(shù),執(zhí)行并把setState的結(jié)果更新到this.state上
if (this.componentWillMount) {
this.componentWillMount();
// When mounting, calls to `setState` by `componentWillMount` will set
// `this._pendingState` without triggering a re-render.
if (this._pendingState) {
this.state = this._pendingState;
this._pendingState = null;
}
}
// 如果聲明了componentDidMount省古,則把其加入到ReactOnDOMReady隊(duì)列中
if (this.componentDidMount) {
transaction.getReactOnDOMReady().enqueue(this, this.componentDidMount);
}
// 調(diào)用組件聲明的render函數(shù)粥庄,并返回ReactComponent抽象類實(shí)例(ReactComponsiteComponent或
// ReactNativeComponent),調(diào)用相應(yīng)的mountComponent函數(shù)
this._renderedComponent = this._renderValidatedComponent();
// Done with mounting, `setState` will now trigger UI changes.
this._compositeLifeCycleState = null;
this._lifeCycleState = ReactComponent.LifeCycle.MOUNTED;
return this._renderedComponent.mountComponent(rootID, transaction);
},
這個(gè)函數(shù)式VDom中最為重要的函數(shù)豺妓,操作也最為復(fù)雜惜互,執(zhí)行操作大概如下:
如上,很多內(nèi)容跟我們這part有點(diǎn)超綱琳拭。當(dāng)然训堆,后面都會(huì)說道,關(guān)于react的渲染白嘁,其實(shí)我們的工作很簡單坑鱼,不關(guān)于任何,在拿到render的東西后,如何解析鲁沥,其實(shí)就是最后一行代碼:this._renderedComponent.mountComponent(rootID, transaction);
mountComponent: function(rootID, transaction) {
ReactComponent.Mixin.mountComponent.call(this, rootID, transaction);
assertValidProps(this.props);
return (
this._createOpenTagMarkup() +
this._createContentMarkup(transaction) +
this._tagClose
);
},
_createOpenTagMarkup: function() {
var props = this.props;
var ret = this._tagOpen;
for (var propKey in props) {
if (!props.hasOwnProperty(propKey)) {
continue;
}
var propValue = props[propKey];
if (propValue == null) {
continue;
}
if (registrationNames[propKey]) {
putListener(this._rootNodeID, propKey, propValue);
} else {
if (propKey === STYLE) {
if (propValue) {
propValue = props.style = merge(props.style);
}
propValue = CSSPropertyOperations.createMarkupForStyles(propValue);
}
var markup =
DOMPropertyOperations.createMarkupForProperty(propKey, propValue);
if (markup) {
ret += ' ' + markup;
}
}
}
return ret + ' id="' + this._rootNodeID + '">';
},
/**
* Creates markup for the content between the tags.
*
* @private
* @param {ReactReconcileTransaction} transaction
* @return {string} Content markup.
*/
_createContentMarkup: function(transaction) {
// Intentional use of != to avoid catching zero/false.
var innerHTML = this.props.dangerouslySetInnerHTML;
if (innerHTML != null) {
if (innerHTML.__html != null) {
return innerHTML.__html;
}
} else {
var contentToUse = this.props.content != null ? this.props.content :
CONTENT_TYPES[typeof this.props.children] ? this.props.children : null;
var childrenToUse = contentToUse != null ? null : this.props.children;
if (contentToUse != null) {
return escapeTextForBrowser(contentToUse);
} else if (childrenToUse != null) {
return this.mountMultiChild(
flattenChildren(childrenToUse),
transaction
);
}
}
return '';
},
function ReactNativeComponent(tag, omitClose) {
this._tagOpen = '<' + tag + ' ';
this._tagClose = omitClose ? '' : '</' + tag + '>';
this.tagName = tag.toUpperCase();
}
代碼稍微多一點(diǎn)呼股,但是工作目標(biāo)很單一,就是為了將描述jsx的obj解析成HTML string画恰。其實(shí)可以參照我上面直接亮出來的自己寫的代碼部分彭谁。
如上,其實(shí)我們已經(jīng)完成了組件的初始化允扇、渲染~
好吧缠局,我們一直說的渲染的核心部分還沒有細(xì)說~~~
掛載組件ref到this.refs上,設(shè)置生命周期考润、狀態(tài)和rootID
mountComponent: function(rootID, transaction) {
invariant(
this._lifeCycleState === ComponentLifeCycle.UNMOUNTED,
'mountComponent(%s, ...): Can only mount an unmounted component.',
rootID
);
var props = this.props;
if (props.ref != null) {
ReactOwner.addComponentAsRefTo(this, props.ref, props[OWNER]);
}
this._rootNodeID = rootID;
this._lifeCycleState = ComponentLifeCycle.MOUNTED;
// Effectively: return '';
},
如果組件ref屬性為空狭园,則為組件的this.refs上掛在當(dāng)前組件,也就是this糊治,實(shí)現(xiàn)如下:
addComponentAsRefTo: function(component, ref, owner) {
owner.attachRef(ref, component);
}
attachRef: function(ref, component) {
var refs = this.refs || (this.refs = {});
refs[ref] = component;
},
上述代碼我刪除了相關(guān)的判斷警告唱矛。
設(shè)置組件生命狀態(tài)
組件的生命狀態(tài)和生命周期鉤子函數(shù)是react的兩個(gè)概念,在react中存在兩種生命周期
- 主:組件生命周期:_lifeCycleState,用來校驗(yàn)react組件在執(zhí)行函數(shù)時(shí)狀態(tài)值是否正確
- 輔:復(fù)合組件生命周期:_componsiteLifeCycleState,用來保證setState流程不受其他行為影響
_lifeCycleState
var ComponentLifeCycle = keyMirror({
/**
* Mounted components have a DOM node representation and are capable of
* receiving new props.
*/
MOUNTED: null,
/**
* Unmounted components are inactive and cannot receive new props.
*/
UNMOUNTED: null
});
組件生命周期非常簡單俊戳,就枚舉了兩種,MOUNTED and UNMOUNTED
在源碼中使用其只是為了在相應(yīng)的階段觸發(fā)時(shí)候校驗(yàn)馆匿,并且給出錯(cuò)誤提示
getDOMNode: function() {
invariant(
ExecutionEnvironment.canUseDOM,
'getDOMNode(): The DOM is not supported in the current environment.'
);
invariant(
this._lifeCycleState === ComponentLifeCycle.MOUNTED,
'getDOMNode(): A component must be mounted to have a DOM node.'
);
var rootNode = this._rootNode;
if (!rootNode) {
rootNode = document.getElementById(this._rootNodeID);
if (!rootNode) {
// TODO: Log the frequency that we reach this path.
rootNode = ReactMount.findReactRenderedDOMNodeSlow(this._rootNodeID);
}
this._rootNode = rootNode;
}
return rootNode;
},
_compositeLifeCycleState
復(fù)合組件的生命周期只在一個(gè)地方使用:setState
var CompositeLifeCycle = keyMirror({
/**
* Components in the process of being mounted respond to state changes
* differently.
*/
MOUNTING: null,
/**
* Components in the process of being unmounted are guarded against state
* changes.
*/
UNMOUNTING: null,
/**
* Components that are mounted and receiving new props respond to state
* changes differently.
*/
RECEIVING_PROPS: null,
/**
* Components that are mounted and receiving new state are guarded against
* additional state changes.
*/
RECEIVING_STATE: null
});
replaceState: function(completeState) {
var compositeLifeCycleState = this._compositeLifeCycleState;
invariant(
this._lifeCycleState === ReactComponent.LifeCycle.MOUNTED ||
compositeLifeCycleState === CompositeLifeCycle.MOUNTING,
'replaceState(...): Can only update a mounted (or mounting) component.'
);
invariant(
compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE &&
compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING,
'replaceState(...): Cannot update while unmounting component or during ' +
'an existing state transition (such as within `render`).'
);
this._pendingState = completeState;
// Do not trigger a state transition if we are in the middle of mounting or
// receiving props because both of those will already be doing this.
if (compositeLifeCycleState !== CompositeLifeCycle.MOUNTING &&
compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_PROPS) {
this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_STATE;
var nextState = this._pendingState;
this._pendingState = null;
var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
transaction.perform(
this._receivePropsAndState,
this,
this.props,
nextState,
transaction
);
ReactComponent.ReactReconcileTransaction.release(transaction);
this._compositeLifeCycleState = null;
}
},
setState會(huì)調(diào)用replaceState ,然后調(diào)用_receivePropsAndState來更新界面
如果組件正處在mounting的過程或者接受props的過程中抑胎,那么將state緩存在_pendingState中兽掰,并不會(huì)更新界面的值方面。
校驗(yàn)props
_assertValidProps: function(props) {
var propDeclarations = this.constructor.propDeclarations;
var componentName = this.constructor.displayName;
for (var propName in propDeclarations) {
var checkProp = propDeclarations[propName];
if (checkProp) {
checkProp(props, propName, componentName);
}
}
}
this.constructor.propDeclarations 就是組件聲明的props屬性历葛,由于props是運(yùn)行時(shí)傳入的屬性唐全。我們可以看到聲明props的屬性值即為checkProp
結(jié)束語
其實(shí)至此衷快,關(guān)于本篇組件的初始化稽亏、渲染已經(jīng)介紹完畢棺牧,由于代碼中關(guān)于太多后續(xù)章節(jié)螃成,生命周期呕臂、props破托、state、對(duì)象緩沖池歧蒋、事務(wù)等土砂,所以暫時(shí)都先略過,后續(xù)學(xué)習(xí)到的時(shí)候再回頭查閱谜洽。