概述
React並不是一個MVVM框架,其實它連一個框架都算不上,它只是一個庫,但是react生態系統中的flux卻是一個MVVM框架,所以我研究了一下flux官方實現中的“雙向綁定”,並記錄下來供以后開發時參考,相信對其他人也有用。
參考資料:
如何監聽 js 中變量的變化?
Flux For Beginners
數據雙向綁定的分析和簡單實現
The ReactJS Controller View Pattern
Controller View
React的核心思想是Controller View設計模式:頂層組件具有所有的state,並把它們作為props向下傳遞給子組件。
這樣的好處有:
- 如果要增加一個相同的子組件,直接增加即可。
- 如果有url參數解析,直接從父組件得到,不需要單獨重復處理。
- 與可變的state相比,靜態的props更加容易理解和預測。
- 方便測試。(只需傳入props即可)
這種設計模式實現的數據流動其實是一種單向流動,即從父組件流向子組件。如果子組件要向父組件傳遞數據,那么只能通過父組件把回調函數作為props傳遞給子組件,然后子組件通過調用這個回調函數來傳遞數據給父組件,這樣就實現了父組件和子組件之間數據的雙向流動。
然而,這個設計模式有一個缺點,就是需要一層一層地向下傳遞數據,如果層級很多的話,就特別麻煩,每個子組件接收的props不全是它需要的數據,還有很多它並不需要但是它的子組件需要的數據。在這種情況下就需要用到flux。
注意:如果層級很少的話,就不建議使用flux或redux。
flux的雙向綁定
我們知道數據的雙向綁定是指:
- view層的用戶修改界面數據,model層的數據也會被修改。這個可以通過瀏覽器自帶的事件響應來解決。
- model層的數據修改會同步到view層畫面的變化。這個時候就涉及到2個方面,一個是model層的數據會渲染到view層,通過react的數據流動即可實現;另一個是model層的數據變化會引起注意,在這個方面,angular是通過臟檢測實現的,vue是通過es5的getter和setter以及Object.defineProperty方法(數據劫持)實現的,那么flux是怎么實現的呢?
flux是通過和瀏覽器類似的事件響應實現,通過事件監聽數據的變化,如果有變化,就引發一個change事件,從而實現同步數據到view層。
事件監聽其實是一種觀察者模式,下面我們來具體討論一下事件監聽的實現。這個實現需要解決下列問題(以change事件為比方):
- 數據具有綁定change事件的方法。
- 數據在change事件發生的時候能夠調用綁定的回調函數。
- 數據在改變的時候能夠觸發change事件。
首先我們要引入MicroEvent.js,這個庫只有一頁代碼,我選取其中重要的部分來講解:
var MicroEvent = function(){};
MicroEvent.prototype = {
bind : function(event, fct){
this._events = this._events || {};
this._events[event] = this._events[event] || [];
this._events[event].push(fct);
},
unbind : function(event, fct){
this._events = this._events || {};
if( event in this._events === false ) return;
this._events[event].splice(this._events[event].indexOf(fct), 1);
},
trigger : function(event /* , args... */){
this._events = this._events || {};
if( event in this._events === false ) return;
for(var i = 0; i < this._events[event].length; i++){
this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));
}
}
};
MicroEvent對象的實例有bind,unbind和trigger方法,分別對應綁定自定義事件,解綁,觸發事件。這樣通過object.asign方法就能把這些方法“給”數據。這就解決了第一個問題。
然后數據在通過bind綁定自定義事件及回調函數之后,可以通過trigger觸發自定義事件並且依次執行綁定在事件上的回調函數。這就解決了第二個問題。
接下來是第三個問題,我們怎么在數據改變的時候觸發事件???
在這一點上,vue是通過es5的getter和setter以及Object.defineProperty方法,通過重寫setter函數,並在里面寫上trigger事件的代碼,實現數據在改變的時候能自動調用trigger方法,從而實現了觸發事件。
但是flux用的並不是這種方法!!!我們先來看一下vue里面實現事件響應的過程,數據的任何改動都會觸發Object.defineProperty綁定的setter方法,從而實現調用trigger方法。所以如果我們只定義具體的改動呢?這樣是不是可以不用Object.defineProperty方法?
這就是flux的實現,我們並不是直接修改數據,而是通過定義具體的動作,通過這個動作修改數據。
AppDispatcher.dispatch({
actionName: 'new-item',
newItem: { name: 'Marco' } // example data
});
而這個動作在被調用的時候會自動觸發trigger函數,從而實現事件響應!!!
這就是為什么flux里面要分為Actions,Dispatcher,Store的原因。我們並不是直接修改數據,而是通過一個中間層Actions修改數據,這樣這個中間層在被調用的時候會觸發trigger函數,實現事件響應!
其它
值得一提的是,node自帶Events庫,通過這個庫也能夠實現與上面的事件響應。但是MicroEvent.js適用性更廣,它還能夠適用於客戶端。
另外,es6定義了新的雙向綁定機制——Proxy。
Proxy就是對象代理,類似上面的中間層actions,它可以給一個對象綁定一個代理對象,通過這個代理對象來代理原對象的各種行為。
var p = new Proxy(target, handler);
let a = new Proxy({}, {
set: function(obj, prop, value) {
obj[prop] = value;
if (prop === 'zhihu') {
console.log("set " + prop + ": " + obj[prop]);
}
return true;
}
});
a.zhihu = 100;
當然,Proxy的能力遠不止此,還可以實現代理轉發等等。
至於兼容性,Proxy的大部分方法都被各大瀏覽器實現了,只有少數幾個方法沒有被實現。具體可以看這里:MDN Proxy。