考不上三本也會實現數據綁定(一)

你現在所閱讀的並不是第一篇文章,你可能想看目錄和前言。

前言

經歷了GacUI的一次超級大重構之後,終於又有空寫文章了。這次重構在保留了所有功能的前提下,刪掉了很多類跟介面,積累了很多魂來傳火,過些日子再圍繞相關的話題來談一談重構。今天先說一下數據綁定的事情。

MVVM

在我講數據綁定的時候,背景是設定在 MVVM模式 上面的,在這裡簡單介紹一下MVVM。MVVM把我們的程序——其實大多數時候就是UI或者網頁——分成了三大塊,分別是Model、ViewModel和View。Model很好理解,指的是數據源,通常是資料庫或者文件。View也很好理解,就是最終的UI,或者是測試用例,或者是一些別的東西。那ViewModel是什麼呢?大多數時候我們也不用死摳概念。ViewModel出現的根本原因就是,Model和View的結構經常是不一樣的。

舉個簡單的例子,Model是每門科目各一份的成績單,View是教室門口貼的總分前10名的大紅榜。顯然Model和View的結構就是完全不同的。在這裡Model按照科目來分,告訴你每個人的成績是多少。View則告訴你總分前10名都是什麼人。那ViewModel是啥呢?其實ViewModel在這裡就是紅榜的內容了。View——紅,ViewModel——榜,沒毛病(逃

因此ViewModel大多數時候就是用來負責實現具體的計算過程的,譬如從一堆成績單裡面算出總分前10名,這就是ViewModel要做的事情。那麼針對這份ViewModel,我們就可以做一堆View,譬如說:

  • 讓老師把前10名念出來

  • 寫進大紅榜貼在教室門口

  • 列印成文件給校長用來做發獎學金的參考

這三個View雖然長的完全不同,但是其內容都是一致的,而且的格式跟Model有巨大的區別。所以這就是ViewModel存在的原因,你需要一些複雜的計算,而且這些計算可以被重複使用,所以要獨立出來,正確處理好依賴:讓View去依賴ViewModel。

數據綁定

好了,那為什麼會有數據綁定呢?因為到了這一步,ViewModel的格式跟View其實就差不多了,那麼你把數據從ViewModel複製到View,或者從View反饋回ViewModel,理論上只需要一些簡單的步驟就可以實現。UWP就假設這些步驟可以簡單到你只需要給一串(通常只有一個)屬性的名字就好了,複雜點的可以用Converter,再複雜就證明你的ViewModel做的不好。

GacUI的數據綁定並沒有這個假設,我可以讓你寫無限複雜的表達式,不過同時也付出了沒有雙向綁定的代價——如果你需要雙向綁定,那你就正反兩邊都綁定好了——因為GacUI的數據綁定除了聯繫View和ViewModel以外,還有別的事情要做。

UWP的x:bind提供了三種綁定的形式,分別是一次性綁定、單向綁定跟雙向綁定。其中一次性綁定就跟寫在構造函數里沒有區別,而雙向綁定可以理解為從一個單向綁定計算出相反的單向綁定要怎麼寫然後自動替你寫好,因此我們就只需要考慮單向綁定要怎麼做就可以了。

在這裡不得不提到的是,我看到有些JavaScript的界面庫在實現數據綁定的時候,採用了一些花式作死的方法,其中我印象最深刻的一種就是,他先跑一下你的表達式,看看一共用到了多少屬性,然後挨個給他們掛上事件處理程序。居然存在使用採樣的方式來實現的編譯器,不得不佩服作者的想像力實在是太強大了,是誰這麼做我已經不記得了。不過他根本不需要這麼做,只要把JavaScript換成TypeScript,這個事情就可以完美的解決。

為什麼呢?因為你既然要做數據綁定,那你總要知道你寫的這個表達式到底需要響應多少事件——其實也就是說,到底要在ViewModel的什麼部分被修改的情況下刷新View——這樣才能做出完整的功能。而JavaScript這個語言在閱讀的時候是沒有上下文的,所以你根本沒辦法做這樣的計算,因此才需要用採樣的方法。而採樣的一個缺點就是,萬一你的表達式有分支怎麼辦?有些人可能會說,你不應該寫分支。其實這個說法很對,畢竟這麼複雜的邏輯應該放進ViewModel里,但是與此配套的,你應該在遇到分支的時候直接爆炸,而不是就這麼默默的接受了。TypeScript就是有上下文的,所以可以清楚地把依賴關係都計算出來。

所以這個系列的前提就這麼確定了,我們在MVVM的模式下,使用一個強類型的語言做數據綁定。那我們需要依靠什麼三本的知識來實現數據綁定,就是接下來的文章要講述的事情。當然說是說系列,多半(二)就會把所有的事情都說完了,畢竟數據綁定的內容很少(逃

一個例子

在文章結束之前,最後講一下我們要如何對數據綁定這個功能進行建模。在GacUI使用的 Workflow腳本語言 裡面,我給了一個bind表達式,具體的用法就是:

bind(obj.A + obj.B)

這個表達式會返回下面的介面的實例:

interface system::Subscription{ /* 一些無關緊要的其他函數 */ event ValueChanged(object);}

然後只要obj.A + obj.B這個表達式改變了——其實也就是A或者B屬性的其中一個發生了變化,ValueChanged事件就會觸發,參數就是這個表達式當前的值。你只要掛了這個事件,自然就可以把最新的數據顯示到UI上面了。譬如說下面的這個測試用例:

module test;using test::*;using system::*;var s = "";func Callback(value : object) : void{ s = $"$(s)[$(cast string value)]";}func main() : string{ var x = new ObservableValue^(); var subscription = bind($"The value has been changed to $(x.Value)"); subscription.Open(); attach(subscription.ValueChanged, Callback); x.Value = 10; x.Value = 20; x.Value = 30; subscription.Close(); return s;}

最終就會返回下面這個字元串:

[The value has been changed to 10][The value has been changed to 20][The value has been changed to 30]

因為x.Value一共改變了3次。

待續

今天這篇文章就說到這裡了,接下來我們會重點描述下面的三個問題

  • 如何跟蹤屬性變化

  • 如何分析屬性之間的依賴關係

  • 如何把數據綁定重寫為對回調函數的調用

這三個問題搞定了,數據綁定也就做出來了。敬請期待。


推薦閱讀:

收藏指數滿格!幫你打包前端之巔一整年好文!
San - 一個傳統的MVVM組件框架
組件化必殺技:styled-components 簡明教程【附視頻下載】
有關Bootstrap你想要知道的都在這裡

TAG:成人教育 | 数据绑定 | 前端框架 |