組件即是函數(shù)
對(duì)于一個(gè)獨(dú)立組件而言,你可以把它看成是一個(gè)JavaScript函數(shù)。對(duì)于函數(shù)而言,當(dāng)你通過傳遞參數(shù)調(diào)用函數(shù)時(shí),函數(shù)會(huì)返回給你一個(gè)值。 相比之下,對(duì)于React組件而言,道理是相似的,你傳遞屬性給組件,而組件則會(huì)返回一個(gè)被渲染好的DOM。通過傳遞不同的數(shù)據(jù), 相應(yīng)的你會(huì)得到不同的響應(yīng)。這個(gè)過程也就使得React組件能夠得到極大的復(fù)用,并且你可以很輕松的在應(yīng)用中重用和組裝這些組件。 這種思想實(shí)際來(lái)源于函數(shù)式編程,但其并不在本文的討論范圍中。如果你感興趣的話,那我強(qiáng)烈建議你閱讀Mikael Brevik的 《 Functional UI and Components as Higher Order Functions》, 它可以幫助你更好的理解這個(gè)話題。
自上而下渲染
目前為止,我們可以輕松地通過組裝組件的方式來(lái)構(gòu)建應(yīng)用,但此之前,在組件中并沒有包括數(shù)據(jù)。在篇文章中,我們討論過可以在組件層次中的根組件中通過將數(shù)據(jù)作為參數(shù)傳遞給組件,并且通過層層傳遞的方式將數(shù)據(jù)傳遞給下層組件,也就是說,你在頂層傳遞數(shù)據(jù),它可以一層一層地往下傳遞, 這個(gè)過程,我們稱之為自上而下的渲染。
從上層組件往下層組件傳遞數(shù)據(jù)其實(shí)很簡(jiǎn)單,但是,如果下層組件發(fā)生了某些變化,我們?nèi)绾瓮ㄖ蠈咏M件呢?例如,用戶點(diǎn)擊了某個(gè)按鈕? 我們需要某個(gè)東西來(lái)存放應(yīng)用程序的狀態(tài)數(shù)據(jù),能夠在狀態(tài)發(fā)生變化的時(shí)候去通知所發(fā)生的變化。新的狀態(tài)應(yīng)該能夠被傳遞給根節(jié)點(diǎn) (上層節(jié)點(diǎn)),然后應(yīng)該再次發(fā)起自上而下的渲染,從而重新生成(渲染)DOM。為了解決這個(gè)問題,F(xiàn)acebook提出了Flux架構(gòu)。
Flux架構(gòu)
你可能已經(jīng)聽過什么是Flux,也了解它是一種類似于MVC的應(yīng)用程序設(shè)計(jì)架構(gòu),因此本文不會(huì)過多的去探討什么是Flux,感興趣的話, 可以閱讀《 Flux Inspired libraires with React》這篇文章。
| 構(gòu)建用戶界面的應(yīng)用程序架構(gòu) —— Facebook Flux |
簡(jiǎn)單總結(jié)下:Flux倡導(dǎo)的是單向數(shù)據(jù)流的原則,在這種架構(gòu)下,通過Store存放應(yīng)用程序的狀態(tài)數(shù)據(jù)。當(dāng)應(yīng)用狀態(tài)發(fā)生變化時(shí),Store可以發(fā)出事件,通知應(yīng)用的組件并進(jìn)行組件的重新渲染。另外,Dispatcher起到中央hub的作用,它為組件(View)和Store構(gòu)建起了橋梁。此外,你可以在組件上調(diào)用action,它會(huì)向Store發(fā)起事件。Store正是通過訂閱這些事件,并根據(jù)事件的觸發(fā)來(lái)改變應(yīng)用程序的內(nèi)部狀態(tài)的。
PureRenderMixin
目前對(duì)于我們的應(yīng)用而言,我們通過一個(gè)數(shù)據(jù)store來(lái)存放應(yīng)用的實(shí)際狀態(tài)。我們可以和這個(gè)store進(jìn)行通信,將數(shù)據(jù)傳遞到我們的應(yīng)用上, 當(dāng)組件獲取新的數(shù)據(jù)后,進(jìn)而對(duì)視圖進(jìn)行重新渲染。這聽起來(lái)很贊,但總感覺會(huì)經(jīng)歷很多次的渲染,的確是這樣的!需要記住的是:組件的層次關(guān)系和自然而下的渲染,一切都會(huì)根據(jù)新的數(shù)據(jù)來(lái)進(jìn)行響應(yīng),做出相應(yīng)的變化。
在這之前的文章中,我們討論過虛擬DOM通過一種更為優(yōu)雅的方式降低了DOM操縱帶來(lái)的性能損耗,但這并不意味著我們就不需要自己手動(dòng)進(jìn)行性能優(yōu)化了。 對(duì)此,基于當(dāng)前數(shù)據(jù)和新來(lái)的數(shù)據(jù)之間的差異,我們應(yīng)該能夠告訴組件對(duì)于新來(lái)的數(shù)據(jù)是否需要進(jìn)行視圖的重新渲染(如果數(shù)據(jù)沒有發(fā)生變化,應(yīng)該不再重新渲染)。在React的生命周期中,你可以借助shouldComponentUpdate來(lái)達(dá)成這一目的。
幸運(yùn)的是,在React中有一種被稱為PureRenderMixin的Mixin模式,它可以用來(lái)對(duì)新的屬性和之前的屬性進(jìn)行對(duì)比,如果是數(shù)據(jù)沒有發(fā)生變化,就不再重新渲染。在內(nèi)部實(shí)現(xiàn)上,它也是基于shouldComponentUpdate 方法的。
這聽起來(lái)很贊,但遺憾的是,PureRenderMixin并不能很好地進(jìn)行對(duì)象的比較。它只會(huì)檢查對(duì)象引用的相等性(===),也就是說, 對(duì)于有相同數(shù)據(jù)的不同對(duì)象而言它會(huì)返回false。
[js] view plaincopy
boolean shouldComponentUpdate(object nextProps, object nextState)
如果shouldComponentUpdate返回的是false的話,render函數(shù)便會(huì)跳過,直到狀態(tài)再次發(fā)生改變。(此外,componentWillUpdate 和componentDidUpdate也會(huì)被跳過)。對(duì)于上面所說的問題,我們可以簡(jiǎn)單的舉個(gè)例子來(lái)說明,有代碼如下:
[js] view plaincopy
var a = { foo: 'bar' };
var b = { foo: 'bar' };
a === b; // false
可以看到,數(shù)據(jù)是相同的,但它們隸屬于不同對(duì)象的引用,因此返回的是false,也因此組件仍然會(huì)進(jìn)行重新渲染,顯然這沒有達(dá)到我們的目的。 如果我們想要達(dá)成設(shè)想的效果(即對(duì)于相同數(shù)據(jù)而言,組件不再重新渲染),我們就需要在原始的對(duì)象上進(jìn)行數(shù)據(jù)的修改:
[js] view plaincopy
var a = { foo: 'bar' };
var b = a;
b.foo = 'baz';
a === b; // true
雖然實(shí)現(xiàn)一個(gè)能夠進(jìn)行深度對(duì)象比較的mixin來(lái)代替引用檢查并不困難,但是,考慮到React調(diào)用shouldComponentUpdate方法非常頻繁,并且對(duì)象的深度檢查代價(jià)較高,所以React選擇了這種對(duì)象引用比較的方案。
我非常建議你閱讀Facebook官方的 有關(guān)React應(yīng)用高級(jí)性能的文檔。
不變性 Immutability
如果我們的應(yīng)用狀態(tài)是一個(gè)單一的、大的、嵌套的對(duì)象(類似于Flux中的Store),那么上面提到的問題會(huì)逐漸升級(jí)。
所以當(dāng)對(duì)象的內(nèi)容沒有發(fā)生變化時(shí),或者有一個(gè)新的對(duì)象進(jìn)來(lái)時(shí),我們傾向于保持對(duì)象引用的不變。這個(gè)工作正是我們需要借助Facebook的Immutable.js來(lái)完成的。
| 不變性意味著數(shù)據(jù)一旦創(chuàng)建就不能被改變,這使得應(yīng)用開發(fā)更為簡(jiǎn)單,避免保護(hù)性拷貝(defensive copy),并且使得在簡(jiǎn)單的應(yīng)用邏輯中實(shí)現(xiàn)變化檢查機(jī)制等。 |
下面通過一個(gè)例子來(lái)解釋下上面的話。比如,有如下的代碼片段:
如上,我們可以使用===來(lái)通過引用來(lái)比較對(duì)象,這意味著我們能夠方便快速的進(jìn)行對(duì)象比較,并且它能夠和React中的PureRenderMixin 兼容?;诖?,我們可以在整個(gè)應(yīng)用構(gòu)建中使用Immutable.js。也就是說,我們的Flux Store應(yīng)該是一個(gè)具有不變性的對(duì)象,并且我們通過 將具有不變性的數(shù)據(jù)作為屬性傳遞給我們的應(yīng)用程序。
現(xiàn)在我們回到前面的代碼片段來(lái)重新想象我們應(yīng)用程序的組件結(jié)構(gòu),可以用下面這張圖來(lái)表示:
如上,我們可以使用===來(lái)通過引用來(lái)比較對(duì)象,這意味著我們能夠方便快速的進(jìn)行對(duì)象比較,并且它能夠和React中的PureRenderMixin 兼容?;诖?,我們可以在整個(gè)應(yīng)用構(gòu)建中使用Immutable.js。也就是說,我們的Flux Store應(yīng)該是一個(gè)具有不變性的對(duì)象,并且我們通過 將具有不變性的數(shù)據(jù)作為屬性傳遞給我們的應(yīng)用程序。
現(xiàn)在我們回到前面的代碼片段來(lái)重新想象我們應(yīng)用程序的組件結(jié)構(gòu),可以用下面這張圖來(lái)表示:
如上,我們可以使用===來(lái)通過引用來(lái)比較對(duì)象,這意味著我們能夠方便快速的進(jìn)行對(duì)象比較,并且它能夠和React中的PureRenderMixin 兼容?;诖耍覀兛梢栽谡麄€(gè)應(yīng)用構(gòu)建中使用Immutable.js。也就是說,我們的Flux Store應(yīng)該是一個(gè)具有不變性的對(duì)象,并且我們通過 將具有不變性的數(shù)據(jù)作為屬性傳遞給我們的應(yīng)用程序。
現(xiàn)在我們回到前面的代碼片段來(lái)重新想象我們應(yīng)用程序的組件結(jié)構(gòu),可以用下面這張圖來(lái)表示:
[js] view plaincopy
var stateV1 = Immutable.fromJS({
users: [
{ name: 'Foo' },
{ name: 'Bar' }
]
});
var stateV2 = stateV1.updateIn(['users', 1], function () {
return Immutable.fromJS({
name: 'Barbar'
});
});
stateV1 === stateV2; // false
stateV1.getIn(['users', 0]) === stateV2.getIn(['users', 0]); // true
stateV1.getIn(['users', 1]) === stateV2.getIn(['users', 1]); // false
如上,我們可以使用===來(lái)通過引用比較對(duì)象,這意味著我們能夠方便快速地進(jìn)行對(duì)象比較,并且它能夠和React中的PureRenderMixin 兼容?;诖?,我們可以在整個(gè)應(yīng)用構(gòu)建中使用Immutable.js。也就是說,我們的Flux Store應(yīng)該是一個(gè)具有不變性的對(duì)象,并且我們通過將具有不變性的數(shù)據(jù)作為屬性傳遞給我們的應(yīng)用程序。
現(xiàn)在我們回到前面的代碼片段來(lái)重新想象我們應(yīng)用程序的組件結(jié)構(gòu),可以用下面這張圖來(lái)表示:
從上面的圖形中你可以發(fā)現(xiàn),在應(yīng)用狀態(tài)發(fā)生變化后,只有紅色的部分會(huì)被重新渲染,因?yàn)槠渌糠值囊脭?shù)據(jù)并沒有發(fā)生變化。也就是說, 只有根組件和其中一部分的user組件會(huì)被重新渲染。
基于這種不變性,能夠優(yōu)化React組件的渲染路徑,并通過這種方式來(lái)重新思考我們的應(yīng)用構(gòu)建和應(yīng)用性能優(yōu)化。此外,得益于虛擬DOM, 它能夠讓React應(yīng)用比傳統(tǒng)應(yīng)用來(lái)得更加高效與快速。
本站文章版權(quán)歸原作者及原出處所有 。內(nèi)容為作者個(gè)人觀點(diǎn), 并不代表本站贊同其觀點(diǎn)和對(duì)其真實(shí)性負(fù)責(zé),本站只提供參考并不構(gòu)成任何投資及應(yīng)用建議。本站是一個(gè)個(gè)人學(xué)習(xí)交流的平臺(tái),網(wǎng)站上部分文章為轉(zhuǎn)載,并不用于任何商業(yè)目的,我們已經(jīng)盡可能的對(duì)作者和來(lái)源進(jìn)行了通告,但是能力有限或疏忽,造成漏登,請(qǐng)及時(shí)聯(lián)系我們,我們將根據(jù)著作權(quán)人的要求,立即更正或者刪除有關(guān)內(nèi)容。本站擁有對(duì)此聲明的最終解釋權(quán)。