fancy

簡單聊一下 One-way data flow、Two-way data binding 與前端框架

新手在學完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不可以嗎?

其實可以,那就會像這樣:

而清空列表的程式碼會變這樣:

任何操作都從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函式:

運用Underscore.js提供的模板系統:

在render函式用jQuery正式將模板系統的輸出塞進HTML裡面;

官方提供的幾個機制就達成data binding了。

這也是為什麼很多人說「Backbone.js是一個one-way data binding框架」的原因。

(其實正確說法應該是:「Backbone.js是一個讓人很容易做到 one-way data binding的框架」。)

乙方向data binding

先去監聽UI元件的變化,一變化就call update函式:

在update函式去更新data model:

評論

乙方向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底下:

使用{{}}的模板syntax:

乙方向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的方式設定資料:

或是用state的方式設定資料:

然後在render函式裡面寫獨特又優雅的jsx語法:

一律由state或是props一層一層將data往子元件傳,超級優雅的甲方向one-way data binding寫法。

這就是一談到one-way data flow,人們就想到React的原因。

乙方向data binding

寫一個監聽變化的函式,一監聽到就去更新data model:

然後設定好onChange這個prop即可:

評論

乙方向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

http://stackoverflow.com/questions/30590170/simple-practical-example-for-two-way-data-binding-in-angularjs

(Photo via TheGiantVermin, CC licensed.)

  • http://tonypai.droppages.com/ tonypai

    React 做 2-way data binding 比起 ng 真的挺麻煩的 :P

    感謝分享 幫助我釐清不少觀念 :)

    • 阿川先生

      you are welcome!

  • Jackson Xu

    覺得這篇文章讓人容易理解DATA BINDING!

    謝謝

    • 阿川先生

      3Q~

  • Bible Tang

    親愛的阿川先生:
    最近剛好在try Angular 跟 React,看了你這篇的整理,觀念很容易就釐清,謝囉

    • 阿川先生

      A”A

  • http://evendesign.tw evenwu

    超完整的說明,感謝你的貢獻!

    • 阿川先生

      謝嚕 ㄏㄏ

  • 王副社

    這篇文章真的太神了,一定要登入進來讚揚一下XD

    • 阿川先生

      水喔 謝謝支持嚕

  • 林萬霖

    說明的很清楚詳細,謝謝!