React中的“雙向綁定”


概述

React並不是一個MVVM框架,其實它連一個框架都算不上,它只是一個庫,但是react生態系統中的flux卻是一個MVVM框架,所以我研究了一下flux官方實現中的“雙向綁定”,並記錄下來供以后開發時參考,相信對其他人也有用。

參考資料:
如何監聽 js 中變量的變化?
Flux For Beginners
數據雙向綁定的分析和簡單實現
The ReactJS Controller View Pattern

Controller View

React的核心思想是Controller View設計模式:頂層組件具有所有的state,並把它們作為props向下傳遞給子組件。

這樣的好處有:

  1. 如果要增加一個相同的子組件,直接增加即可。
  2. 如果有url參數解析,直接從父組件得到,不需要單獨重復處理。
  3. 與可變的state相比,靜態的props更加容易理解和預測。
  4. 方便測試。(只需傳入props即可)

這種設計模式實現的數據流動其實是一種單向流動,即從父組件流向子組件。如果子組件要向父組件傳遞數據,那么只能通過父組件把回調函數作為props傳遞給子組件,然后子組件通過調用這個回調函數來傳遞數據給父組件,這樣就實現了父組件和子組件之間數據的雙向流動

然而,這個設計模式有一個缺點,就是需要一層一層地向下傳遞數據,如果層級很多的話,就特別麻煩,每個子組件接收的props不全是它需要的數據,還有很多它並不需要但是它的子組件需要的數據。在這種情況下就需要用到flux。

注意:如果層級很少的話,就不建議使用flux或redux。

flux的雙向綁定

我們知道數據的雙向綁定是指:

  1. view層的用戶修改界面數據,model層的數據也會被修改。這個可以通過瀏覽器自帶的事件響應來解決。
  2. model層的數據修改會同步到view層畫面的變化。這個時候就涉及到2個方面,一個是model層的數據會渲染到view層,通過react的數據流動即可實現;另一個是model層的數據變化會引起注意,在這個方面,angular是通過臟檢測實現的,vue是通過es5的getter和setter以及Object.defineProperty方法(數據劫持)實現的,那么flux是怎么實現的呢?

flux是通過和瀏覽器類似的事件響應實現,通過事件監聽數據的變化,如果有變化,就引發一個change事件,從而實現同步數據到view層。

事件監聽其實是一種觀察者模式,下面我們來具體討論一下事件監聽的實現。這個實現需要解決下列問題(以change事件為比方):

  1. 數據具有綁定change事件的方法。
  2. 數據在change事件發生的時候能夠調用綁定的回調函數。
  3. 數據在改變的時候能夠觸發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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM