談談React的Flux架構和Immutable.js
在之前的文章中,我們討論了虛擬DOM的概念,以及如何使用組件的思維方式來思考開發。現在,是時候在將它們組合起來運用到實戰中去, 并且探討React的組件間是如何進行相互通信的。
原文地址
https://blog.risingstack.com/the-react-js-way-flux-architecture-with-immutable-js/
</blockquote>本文是React系列的第二篇文章,點擊鏈接可以查看第一篇文章, 如果你對一些基礎知識不是很熟,我強烈推薦你閱讀一下第一篇文章。
組件即是函數
對于一個獨立組件而言,你可以把它看成是一個JavaScript函數。對于函數而言,當你通過傳遞參數調用函數時,函數會返回給你一個值。 相比之下,對于React組件而言,道理是相似的,你傳遞屬性給組件,而組件則會返回一個被渲染好的DOM。通過傳遞不同的數據, 相應的你會得到不同的響應。這個過程也就使得React組件能夠得到極大的復用,并且你可以很輕松的在應用中重用和組裝這些組件。 這種思想實際來源于函數式編程,但其并不在本文的討論范圍中。如果你感興趣的話,那我強烈建議你閱讀Mikael Brevik的 Functional UI and Components as Higher Order Functions, 它可以幫助你更好的理解這個話題。
自上而下渲染
目前為止,我們可以輕松的通過組裝組件的方式來構建應用,但此之前,在組件中并沒有包括數據。在前一篇文章中,我們討論過可以在組件層次中的根組件中 通過將數據作為參數傳遞給組件,并且通過層層傳遞的方式將數據傳遞給下層組件,也就是說,你在頂層傳遞數據,它可以一層一層的往下傳遞, 這個過程,我們稱之為自上而下的渲染。
![]()
從上層組件往下層組件傳遞數據其實很簡單,但是,如果下層組件發生了某些變化,我們如何通知上層組件呢?例如,用戶點擊了某個按鈕? 我們需要某個東西來存放應用程序的狀態數據,能夠在狀態發生變化的時候去通知所發生的變化。新的狀態應該能夠被傳遞給根節點 (最上層節點),然后應該再次發起自上而下的渲染,從而重新生成(渲染)DOM。為了解決這個問題,非死book提出了Flux架構。
Flux架構
你可能已經聽過什么是Flux,也了解它是一種類似于MVC的應用程序設計架構,因此本文不會過多的去探討什么是Flux,感興趣的話, 可以閱讀Flux Inspired libraires with React這篇文章。
構建用戶界面的應用程序架構 —— 非死book Flux
</blockquote>簡單總結下:Flux倡導的是單向數據流的原則,在這種架構下,通過Store存放應用程序的狀態數據。當應用狀態發生變化時,Store可以發出事件,通知應用的組件并進行組件的重新渲染。另外,Dispatcher起到中央hub的作用,它為組件(View)和Store構建 起了橋梁。此外,你可以在組件上調用action,它會向Store發起事件。Store正是通過訂閱這些事件,并根據事件的觸發來改變 應用程序的內部狀態的。
![]()
PureRenderMixin
目前對于我們的應用而言,我們通過一個數據store來存放應用的實際狀態。我們可以和這個store進行通信,將數據傳遞到我們的應用上, 當組件獲取新的數據后,進而對視圖進行重新渲染。這聽起來很贊,但總感覺會經歷很多次的渲染,的確是這樣的!需要記住的是: 組件的層次關系和自然而下的渲染,一切都會根據新的數據來進行響應,做出相應的變化。
在這之前的文章中,我們討論過虛擬DOM通過一種更為優雅的方式降低了DOM操縱帶來的性能損耗,但這并不意味著我們就不需要自己手動進行性能優化了。 對此,基于當前數據和新來的數據之間的差異,我們應該能夠告訴組件對于新來的數據是否需要進行視圖的重新渲染(如果數據沒有發生變化,應該不再重新渲染)。 在React的生命周期中,你可以借助shouldComponentUpdate來達成這一目的。
幸運的是,在React中有一種被稱為PureRenderMixin的 Mixin模式,它可以用來對新的屬性和之前的屬性進行對比,如果是數據沒有發生變化,就不再重新渲染。在內部實現上,它也是基于shouldComponentUpdate方法的。
這聽起來很贊,但遺憾的是,PureRenderMixin并不能很好的進行對象的比較。它只會檢查對象引用的相等性(===),也就是說, 對于有相同數據的不同對象而言它會返回false。
boolean shouldComponentUpdate(object nextProps, object nextState)如果shouldComponentUpdate返回的是false的話,render函數便會跳過,直到狀態再次發生改變。(此外,componentWillUpdate和componentDidUpdate也會被跳過)。對于上面所說的問題,我們可以簡單的舉個例子來說明,有代碼如下:
var a = { foo: 'bar' };
var b = { foo: 'bar' };a === b; // false</pre>
可以看到,數據是相同的,但它們隸屬于不同對象的引用,因此返回的是false,也因此組件仍然會進行重新渲染,顯然這沒有達到我們的目的。 如果我們想要達成設想的效果(即對于相同數據而言,組件不再重新渲染),我們就需要在原始的對象上進行數據的修改:
var a = { foo: 'bar' }; var b = a; b.foo = 'baz'; a === b; // true雖然實現一個能夠進行深度對象比較的mixin來代替引用檢查并不困難,但是,考慮到React調用shouldComponentUpdate方法非常頻繁,并且對象的 深度檢查代價較高,所以React選擇了這種對象引用比較的方案。
我非常建議你閱讀非死book官方的有關React應用高級性能的文檔。
不變性 Immutability
如果我們的應用狀態是一個單一的、大的、嵌套的對象(類似于Flux中的Store),那么上面提到的問題會逐漸升級。
所以當對象的內容沒有發生變化時,或者有一個新的對象進來時,我們傾向于保持對象引用的不變。這個工作正是我們需要借助非死book的 Immutable.js來完成的。
不變性意味著數據一旦創建就不能被改變,這使得應用開發更為簡單,避免保護性拷貝(defensive copy),并且使得在簡單的應用 邏輯中實現變化檢查機制等。
</blockquote>下面通過一個例子來解釋下上面的話。比如,有如下的代碼片段:
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</pre>如上,我們可以使用===來通過引用來比較對象,這意味著我們能夠方便快速的進行對象比較,并且它能夠和React中的PureRenderMixin兼容。基于此,我們可以在整個應用構建中使用Immutable.js。也就是說,我們的Flux Store應該是一個具有不變性的對象,并且我們通過 將具有不變性的數據作為屬性傳遞給我們的應用程序。
現在我們回到前面的代碼片段來重新想象我們應用程序的組件結構,可以用下面這張圖來表示:
![]()
從上面的圖形中你可以發現,在應用狀態發生變化后,只有紅色的部分會被重新渲染,因為其他部分的引用數據并沒有發生變化。也就是說, 只有根組件和其中一部分的user組件會被重新渲染。
基于這種不變性,能夠優化React組件的渲染路徑,并通過這種方式來重新思考我們的應用構建和應用性能優化。此外,得益于虛擬DOM, 它能夠讓React應用比傳統應用來得更加高效與快速。
來自:http://wwsun.me/posts/flux-getting-started.html