在上一篇《自建仿React引擎(二)》中,已經(jīng)針對React.js里面其中一個重要的對象props的作用以及實(shí)現(xiàn)進(jìn)行了講解,接下來,我準(zhǔn)備講解refs的作用及實(shí)現(xiàn)機(jī)制,在處理這個refs的時候,遇到了不少的麻煩,花了一些時間思考解決方案。
首先,我們來看看refs在React.js中的作用,具體看下圖。
我們在組件中定義ref,代碼示例如下:
編譯后的代碼如下:
注意,在父組件使用到的ref屬性,作為子組件的入?yún)⑦M(jìn)行傳遞。此時,如果按照傳統(tǒng)的做法,先創(chuàng)建父組件,再創(chuàng)建子組件,ref又是子組件的入?yún)ⅲ敲匆獙?shí)現(xiàn)refs的機(jī)制也就很簡單了,直接在父組件的refs對象中,通過指定this.refs[ref] = 子組件進(jìn)行賦值即可,然而,事實(shí)上React的組件創(chuàng)建流程是基于深度優(yōu)先的創(chuàng)建機(jī)制,簡要流程示意圖如下。
React.js組件在渲染的過程中,會創(chuàng)建兩類不同的對象,一個是ReactClass,一個是ReactElement。其中,ReactClass是自頂層至底層創(chuàng)建的順序,而ReactElement則是自底層往頂層創(chuàng)建的順序。
組件渲染時,先通過React.createClass創(chuàng)建出class對象,再通過class對象的render方法創(chuàng)建ReactElement對象,然后通過ReactDOM將ReactElement對象進(jìn)行渲染。因此,實(shí)現(xiàn)refs的關(guān)鍵點(diǎn)就在于在創(chuàng)建子ReactElement對象的時候,要準(zhǔn)確的找到最外層父組件ReactClass對象。
也許有人會說,那簡單啊,在創(chuàng)建子組件的時候,碰到ref的,都通過一個統(tǒng)一的全局變量緩存起來,不就可以解決了嗎?首先,從編譯后的代碼中,我們可以看得出,子組件是沒有任何標(biāo)識被哪個父組件包含,其次,父組件也沒有標(biāo)識這是最外層父組件,再者,子組件也允許有自己內(nèi)部的ref,這樣子通過全局變量緩存起來的解決方案就會相當(dāng)復(fù)雜。(React的實(shí)現(xiàn)是基于對象繼承,以代理的模式實(shí)現(xiàn),有興趣可以去看一下源碼)。
經(jīng)過再三的思考,發(fā)現(xiàn)此處適合用棧模型來解決會比較省事,于是編寫了一個簡單的棧模型,具體代碼如下。
有了這個棧模型之后,我們就可以比較輕松的實(shí)現(xiàn)獲取當(dāng)前父組件class對象了。根據(jù)上面React組件渲染的流程,我們只要在React.createClass的時候,將class對象入棧,在當(dāng)前class創(chuàng)建完ReactElement對象后,將class對象出棧。這樣子就能保證子ReactElement在創(chuàng)建的時候,可以輕松獲取當(dāng)前運(yùn)行環(huán)節(jié)最頂層的組件class對象,將自己與頂層組件class對象中的refs關(guān)聯(lián)起來。
關(guān)鍵代碼示例如下:
1.React.createClass
class.refs = {};//創(chuàng)建refs對象
ClassStacks.push(class);//入棧
2.React.createElement
if(class){//組件
var _node = class.render();
ClassStacks.pop(); //render完成,出棧。
if(props != undefined && props["ref"] != undefined) {
ClassStacks.get().refs[props["ref"]] = class;//此時棧的頂層元素是父組件,綁定的ref對象是組件
}
}else{//DOM元素
if(props != undefined && props["ref"] != undefined) {
ClassStacks.get().refs[props["ref"]] = _dom;//此時棧的頂層元素是父組件,綁定的組件是DOM元素
}
}
經(jīng)過這樣子的改造,我們就完成了組件refs的開發(fā)工作。下期,我們會針對state狀態(tài)管理機(jī)制的實(shí)現(xiàn)進(jìn)行講解,謝謝大家。
引擎全部代碼如下:
/**
- class運(yùn)時期的棧模型
*/
var ClassStacks = (function() {
var vClasses = new Array();
return {
get: function() {
return vClasses[vClasses.length - 1] || {
props: {},
refs: {}
};
},
push: function(cls) {
vClasses.push(cls);
},
pop: function() {
return vClasses.pop();
}
}
})();
var React = {
createClass: function(cfg) { //直接量對象cfg
var _f = function(props) { //將當(dāng)前結(jié)點(diǎn)的屬性存放到直接量對象cfg中的props屬性
if(cfg.getDefaultProps != undefined &&
typeof(cfg.getDefaultProps) === 'function') {
cfg.props = cfg.getDefaultProps();
}
//傳入?yún)?shù)
for(var key in props) {
cfg.props[key] = props[key];
}
cfg.refs = {};//默認(rèn)添加refs對象
cfg.props.children = []; //定義children對象
ClassStacks.push(cfg);//入棧
return cfg;
}
return _f;
},
createElement: function() {
var tagName = arguments[0]; //第1個入?yún)⑹墙Y(jié)點(diǎn)的名稱
var props = arguments[1] || {}; //第2個入?yún)⑹钱?dāng)前節(jié)點(diǎn)的屬性
if(typeof(tagName) == 'string') {
var _node = document.createElement(tagName);
if(props) {
for(var key in props) {
if(key == 'ref') {
ClassStacks.get().refs[value] = _node;//綁定DOM元素
} else{
_node.setAttribute(key, props[key]);
}
}
}
//第3個入?yún)ⅲê竺娴膮?shù)都為當(dāng)前節(jié)點(diǎn)的孩子節(jié)點(diǎn)
for(var index = 2; index < arguments.length; index++) {
_node.appendChild(arguments[index]);
}
return _node;
} else {
//組織處理chidren數(shù)組
var children = [];
for(var index = 2; index < arguments.length; index++) {
var param = arguments[index];
if(param && typeof param === 'object' &&
Array == param.constructor) { //處理children數(shù)組傳遞數(shù)組的情況
children = children.concat(param);
} else {
children.push(param);
}
}
var vDom = tagName(props); //創(chuàng)建Class。
vDom.props.children = children; //存放children對象
var _node = vDom.render(); //執(zhí)行createClass返回的_f函數(shù),執(zhí)行cfg直接量對象里面的render方法
ClassStacks.pop(); //render完成,出棧。
if(props != undefined && props["ref"] != undefined) {
ClassStacks.get().refs[props["ref"]] = vDom;//此時棧的頂層元素是父組件
}
return _node;
}
}
}
var ReactDOM = {
render: function(ele, container) {
container.appendChild(ele);
}
}