新手在學完JavaScript基本知識、離開新手村之後,很快就必須面對前端框架。
這些框架常會號稱是one-way data flow或是two-way data binding。
這兩個名詞究竟代表什麼呢?一定要有框架才能做到嗎?一個框架只能是one-way或是two-way嗎?
這篇文章會一次回答這些觀念問題,並且分別檢視一次Backbone、Angular、React三套框架。
用詞澄清
在開始之前,先針對用詞做兩點澄清。
澄清一:「data flow」跟「data binding」指的是同一件事。
換句話說,one-way data flow就是one-way data binding;two-way data binding就是two-way data flow。
這件事其實Facebook的React官方文件就有提到:
React’s one-way data flow (also called one-way binding) keeps everything modular and fast.
澄清二:這兩個詞是指稱某種行為,而不是指稱具備某種功能。
舉例來說,下面這種句子是不嚴謹、充滿誤會的:
* 某某框架「是」一個 one-way data binding 框架。 → 不嚴謹的句型,盡量少用。
正確的描述方法是這樣:
* 某某框架讓人「很容易做到」one-way data binding。 → 正確句型。
溝通上應該避免用「是」句型;用「很容易做到」句型才正確。
使用正確的句型有助於理解這兩個詞。這部份後面會再解釋。
Data model
data binding指的是UI元件和data model之間的互動。
在談data binding之前,我們先從data model的觀念開始談起。
沒有data model觀念的JavaScript/jQuery寫法
新手最直覺的作法,就是用JavaScript/jQuery不斷操作HTML元件,讓畫面上顯示他要的效果。
不同元件之間需要溝通時,就每次都去觀察HTML元件的內容,將需要的資料找到,接著處理。
舉例來說,做一個TODO list時,就用ul元件包住一堆li元件,每個li元件代表一個待辦事項。
需要增加/減少/修改事項時,就用JavaScript/jQuery想辦法找到目標li元件,然後處理它。
那麼要把所有事項內容用alert跳出來,或是用HTTP POST丟給後端怎麼辦?
那就用JavaScript/jQuery把li的內容分別抓出來,整理成字串丟給alert顯示,或是另組成form元件submit出去(用成Ajax丟出也可以)。
簡單來說,就是data本身存在於UI元件(HTML元件)之中。每次需要data就去分析一次UI元件。
這種作法簡單、直覺,在UI單純時可以這樣寫,但當頁面上的UI元件多、互動又複雜時,程式碼很快就會變成一團混亂。
有data model觀念的JavaScript/jQuery寫法
有經驗的工程師很快就會想到separation of concerns原則,將data本身獨立成自己的模型(本文稱為data model),每次顯示就根據那個data model去render出UI元件即可。
例如說像這樣(todos陣列就是data model):
然後在button元件或是li元件的onclick事件裡面操作todos陣列,就算是操作data model了。
此外,記得在所有關於data model的操作內呼叫renderTodoList函式。
像這樣任何操作都從data model出發,最後再render的作法,可以讓data model跟UI元件保持某種對應關係,讓程式碼更好維護。
有經驗的前端工程師,光靠這種技巧,再搭配Handlebars之類的模板系統,就可以寫出十分漂亮的程式碼。
例如這份:jQuery TodoMVC 、source code
可以反過來寫嗎?
這個時候,就有人疑惑了:要讓data model跟UI保持對應關係,可不只這種方法。
為何要從data model出發再render出UI?何不從UI出發再generate出data model?
每次變動過HTML都去更新data model不可以嗎?
其實可以,那就會像這樣:
- Exercise
- Learn JavaScript
- Write a blog
而清空列表的程式碼會變這樣:
任何操作都從HTML(UI)出發,最後再generateDataModel的作法,一樣可以讓data model跟UI元件保持某種對應關係。
Data binding
有了data model獨立於UI的觀念之後,我們會發現上面兩種作法代表兩種資料的流向(data flow):
* 從data model出發,每次更新就同步更新UI(甲方向)
* 從UI出發,每次更新就同步更新data model(乙方向)
前端框架與data binding的關係,就是框架本身能否讓人很容易就做出甲方向或是乙方向的效果。
能輕易做出其中一個方向,就算是達成one-way data binding。
能輕易同時做出兩方向,就算是達成two-way data binding。
以前面Todo list的兩個範例來說,其實都做到one-way data binding了,但是我需要去多寫一個陣列、幾個函式、用了onclick事件、手動去loop過每個待辦事項、還要記得每次去呼叫函式,花了點功夫(共做5件事)才做到one-way data binding。
所以我們不會說JavaScript本身有data binding機制,也不會說jQuery是一個讓人很容易做到one-way data binding的套件。
這也是最前面「澄清二」提到的避免使用「是」句型的原因:
* 說「jQuery是一個提供one-way data binding的套件」是錯誤的,因為做起來不太容易。
* 說「jQuery不是一個提供one-way data binding的套件」也是錯誤的,因為還是做得到。
* 說「jQuery要做到one-way data binding不太容易」就沒問題,邏輯上正確。
如果每次更新data model就call renderTodoList,再加上,每次更新UI就call generateDataModel,那甚至做到了two-way data binding。(實務上大概不會有人這麼做。)
說明完這兩個詞為什麼是在指稱行為而非特性,然後解釋了句型上的正確用法之後,讓我們來解讀各大前端框架的特性,以及用正確的句子評論它們。
Backbone.js
參考這份程式碼:http://jsfiddle.net/Xm5eH/9/
甲方向data binding
設定data model更新時要call render函式:
this.model.on("change", this.render, this);
運用Underscore.js提供的模板系統:
template: _.template($("#say-template").html()),
在render函式用jQuery正式將模板系統的輸出塞進HTML裡面;
render: function() { this.$el.html(this.template(this.model.toJSON())); }
官方提供的幾個機制就達成data binding了。
這也是為什麼很多人說「Backbone.js是一個one-way data binding框架」的原因。
(其實正確說法應該是:「Backbone.js是一個讓人很容易做到 one-way data binding的框架」。)
乙方向data binding
先去監聽UI元件的變化,一變化就call update函式:
events: { "change #input": "update" },
在update函式去更新data model:
update: function(e) { this.model.set("text", $(e.target).val()); this.model.set("message", "Model value 'text' changed to '" + this.model.get('text') + "'"); },
評論
乙方向data binding很有爭議,也造成Backbone.js社群的一些誤會與爭論。
在某些人看來,這樣夠輕易就達成乙方向data binding了,因此「Backbone.js是一個two-way data binding框架」。
在另外某些人看來,這只是去監聽UI內容的變化然後手動更新data model而已,因此「Backbone.js不是一個two-way data binding框架」。
在最後某些人看來,上面甲乙兩個方向都是一堆手工設定達成,需要更優雅的去綁定兩邊才算數,所以又做了Epoxy.js這樣的套件。
這也再次說明了為什麼要避免使用「是」句型:它帶給人們混亂、誤會與雞同鴨講。
就說你認為「Backbone.js容不容易做到one/two-way data binding」就好了。
因為「容不容易」是一個主觀問題,聽的人各自判斷,誰也不需要說服誰。
Angular
參考這份程式碼:http://plnkr.co/edit/GxqBiOoNFuECn55R4uJZ?p=preview
甲方向data binding
把資料設在$scope底下:
app.controller('MainCtrl', function($scope) { $scope.firstName = 'John'; });
使用{{}}的模板syntax:
First name: {{firstName}}
乙方向data binding
利用ng-model這directive就完成了:
評論
甲乙兩方向都很容易做到,Angular也一直以此為賣點。
這就是一談到two-way data binding,人們就想到Angular的原因。
話雖如此,還是老話一句:不要說「Angular是一個two-way data binding的框架」。
說「Angular讓人很容易做到two-way data binding」比較好。
React
參考這份程式碼:https://jsfiddle.net/reactjs/n47gckhr/
甲方向data binding
用props的方式設定資料:
var PRODUCTS = [ {category: 'Sporting Goods', price: '$49.99', stocked: true, name: 'Football'}, {category: 'Sporting Goods', price: '$9.99', stocked: true, name: 'Baseball'}, {category: 'Sporting Goods', price: '$29.99', stocked: false, name: 'Basketball'}, {category: 'Electronics', price: '$99.99', stocked: true, name: 'iPod Touch'}, {category: 'Electronics', price: '$399.99', stocked: false, name: 'iPhone 5'}, {category: 'Electronics', price: '$199.99', stocked: true, name: 'Nexus 7'} ]; ReactDOM.render(, document.getElementById('container') );
或是用state的方式設定資料:
getInitialState: function() { return { filterText: '', inStockOnly: false }; },
然後在render函式裡面寫獨特又優雅的jsx語法:
render: function() { var rows = []; // 省略資料整理過程 return (
Name | Price |
---|
一律由state或是props一層一層將data往子元件傳,超級優雅的甲方向one-way data binding寫法。
這就是一談到one-way data flow,人們就想到React的原因。
乙方向data binding
寫一個監聽變化的函式,一監聽到就去更新data model:
handleChange: function(event) { this.setState({message: event.target.value}); },
然後設定好onChange這個prop即可:
render: function() { var message = this.state.message; return ; }
評論
乙方向data binding做得到,官方甚至提供Helpers使用,還用Two-Way Binding Helpers描述它。
但是實務上通常會採取某種程度的Flux架構(永遠經手action和store去更新UI),因此不會使用這種Helpers。
所以大家都說「React是one-way data flow」而不會說「React是two-way data binding」。
話雖如此,換個方式,說「React會強迫做到one-way data flow」比較好。
至於React跟two-way data binding的關係就不重要了。
不過就是個做得到但實務上不常用的手法。你覺得是就是,你覺得容易就容易吧。
結論
透過這篇文章的脈絡,你會發現隨便套個模板系統,然後在每次data model更新時用下面任一方式重新render出UI,都算是甲方向data binding:
* 全手動(jQuery範例的呼叫render)
* 半手動(Backbone範例的設定模板與model on事件)
* 自動(Angular與React)
至於乙方向data binding就更多元了:有的看起來像是全手動更新,有的看起來像是半手動監聽事件然後update model,有的看起來像是宣告bindings即可,有的寫個ng-model的directive就完成。
光是甲或乙方向data binding的定義就如此鬆散,自然one-way或是two-way的爭論會應運而生。
其實,在挑選框架的時候,不管是標榜one-way或two-way的框架,背後終究都只是資料傳過來傳過去而已,差別不過在於框架本身替你做掉多少事情。
然而,框架替你做多做少都不是重點,畢竟做多本身也代表入門越困難、convention越多。
只要整體用起來順手、好理解、好維護,那麼任何一種寫法都是最好的寫法。
(完)
參考資料
http://stackoverflow.com/questions/13504906/what-is-two-way-binding
(Photo via TheGiantVermin, CC licensed.)