從零開始編寫一個 MVVM 框架(一)

翻譯的文章再姑,末尾你能找到原文地址~~~


最近缓待,我在知乎上讀到了幾篇有趣的文章,它們在討論 MVVM 相關(guān)的東西栅葡。伴隨著 Angular 和 React 的崛起,MVVM 現(xiàn)在處在了風(fēng)口上尤泽。
在這篇以及后續(xù)的文章中欣簇,我將說明 MVVM 是如何運(yùn)作的,然后我們將寫一個簡易的 MVVM 框架坯约。這第一篇文章內(nèi)容涵蓋了 MVVM 的簡單介紹以及它嘗試解決的問題熊咽。
如果你已經(jīng)是一個老手,請忽略這篇文章闹丐。

MVVM 是啥横殴?它又為何而與眾不同?

MVVM 是 MVC 的一個變種卿拴。如果你知道 MVC衫仑,MVVM 將變得很容易理解。
你可以簡單理解為:

MVVM 是在 adapter 模式中的 MVC堕花。

所有的 M-V-* 模式都有同一個目標(biāo)文狱,那就是提供一種容易理解的方式來組織數(shù)據(jù)和界面的協(xié)同關(guān)系。
而不同點(diǎn)則在于它們?nèi)绾蝿澐指髯缘拇a航徙。
(注意:10 個開發(fā)者對 MVC 可能有 12 種解釋如贷,下面的內(nèi)容純粹是我個人的觀點(diǎn)。)

模型(Model)-視圖(View)-控制器(Controller)和它潛在的問題

MVC 模式試圖將用戶界面(View)和數(shù)據(jù)源(Model)獨(dú)立開到踏,同時由控制器(Controller)來處理用戶輸入杠袱。下面是它的工作原理:

MVC

于是,問題就出來了:
除非你在創(chuàng)建一個數(shù)據(jù)庫可視化系統(tǒng)窝稿,否則你需要針對視圖層和模型層做出適配楣富。但其實(shí)這都不是合適的選擇。模型負(fù)責(zé)處理業(yè)務(wù)邏輯(它是真實(shí)世界的一個抽象)伴榔。視圖重點(diǎn)聚焦展示內(nèi)容纹蝴,所以它的數(shù)據(jù)結(jié)構(gòu)將和它的展示方式高度耦合(而不是與它的展示內(nèi)容相耦合庄萎,因?yàn)槟菚沟媚愕囊晥D層難以復(fù)用)。
如果你對我上面說的不是很明白塘安,那就看看下面的示例糠涛。
假如我們有一個用 JSON 存儲的學(xué)生信息的數(shù)據(jù)集:

{
  "first-name": "Tracy",
  "last-name": "Kennedy",
  "grade": 6,
  "height": 150,
  "weight": 40
}

我們將在一個列表視圖中像這樣展示:

<ul>
  <li>
    <span>Name:</span>
    <span>Tracy Kennedy</span>
  </li>
  <li>
    <span>Grade:</span>
    <span>6</span>
  </li>
  <li>
    <span>Height:</span>
    <span>1.5m</span>
  </li>
  <li>
    <span>Weight:</span>
    <span>40kg</span>
  </li>
</ul>

這就存在這幾個問題:

  1. 原始數(shù)據(jù)中沒有一個叫做 name 的字段,所以我們需要在哪里計算它兼犯?
  2. 我們的 height 字段是以厘米為單位的忍捡,我們需要在哪里轉(zhuǎn)換它嫉拐?
  3. 最煩人的一個怀骤,誰來負(fù)責(zé)將這一個對象形式的數(shù)據(jù)抽象成一個數(shù)組?
    程序中的任何嘗試解決上述問題的一個部分拳氢,其可復(fù)用性和可維護(hù)性都將極大地降低纬霞。我們很容易就傾向于通過繼承來為這種情況創(chuàng)建專用的模型和視圖凌埂。但是,假設(shè)你有在一個復(fù)雜應(yīng)用中有數(shù)十個這種列表視圖诗芜,你可能就要專門記錄繼承樹瞳抓。那將真是一場世界性的噩夢。

龐大的 視圖-控制器

你可能注意到了绢陌,我故意忽略了控制器挨下。事實(shí)上,在傳統(tǒng)的 MVC 架構(gòu)中脐湾,控制器并不算一個單獨(dú)的層級。它總是伴隨著視圖而存在叙淌。視圖和控制器塑造了用戶交互的 視圖-控制器 層秤掌。
但是控制器和視圖又有一點(diǎn)不一樣:它生來就是被污染了的。我是指鹰霍,一個控制器是原本就是用來做適配和轉(zhuǎn)換的(我們使用控制器來將用戶輸入轉(zhuǎn)化為數(shù)據(jù)變動)闻鉴。它原本就和視圖以及模型高度耦合,并且難以復(fù)用茂洒。
是的孟岛,將那些繁雜的工作交給控制器處理看起來是一個不錯的主意,所以我們稍微修改了一下 MVC 模式:


修改后的 MVC

完美督勺!現(xiàn)在我們將所有繁雜的工作都集中到了一個地方渠羞。
但是,你知道我要說“但是……”智哀,對吧次询?
現(xiàn)在你又有了另一個問題。你最終可能在一個類之中包含了成千上萬行的代碼瓷叫,使得它變得難以閱讀和維護(hù)屯吊。最終這種變種被稱為龐大的 視圖-控制器 模式送巡,因?yàn)橐粋€控制器將變得非常巨大和復(fù)雜 :)
我并不是在批判這種嘗試將適配的邏輯從模型和視圖中獨(dú)立出來的嘗試。我是說盒卸,它是一個不錯的嘗試骗爆,但是它可以做得更好。

ViewModel 作為一個適配器

從某種程度上說蔽介,在軟件行業(yè)中越小巧才越受歡迎摘投。人們總是討論更小的類、更小的函數(shù)和更小的組件……因?yàn)樾⊥ǔ5葍r于簡單屉佳,而大則通常意味著復(fù)雜谷朝。
所以,避免過于龐大的控制器帶給我們的復(fù)雜度武花,最簡單的方式就是將它分為幾個部分圆凰。一個龐大的控制器有以下三類主要的邏輯:

  • 用戶界面效果,比如翻頁体箕、滑動……专钉;
  • 用戶界面更新——模型改變了;
  • 指令——用戶操作累铅;
    用戶界面效果顯然不同于另外兩個跃须,它應(yīng)該跟視圖層在一起。
    用戶界面更新呢娃兽?它應(yīng)該屬于處在模型層和視圖層中間的某種存在菇民。
    好了,現(xiàn)在我們已經(jīng)將控制器至少劃分成了兩個獨(dú)立的部分投储。指令呢第练?它該去哪兒?
    此時玛荞,這個有點(diǎn)難以下決定了娇掏。我們先將它放到一邊。
    我們先看一些示例代碼:
    先繼續(xù)我們的學(xué)生信息頁面勋眯。我們將借用 Vue.js 模板的語法婴梧。
<ul>
  <li v-for="item in items">
    <span>{{item.description}}</span>
    <span>{{item.content}}</span>
  </li>
</ul>

上面的代碼意味著我們將遍歷名為 items 的列表。對于每一個其中的成員客蹋,我們將插入一個 <li> 標(biāo)簽并且填入一些這個成員的數(shù)據(jù)到 <span> 標(biāo)簽中塞蹭。
所以,如果我們需要一個轉(zhuǎn)換函數(shù):

function items(student){
    function item(description, content){
        return {
            description: description,
            content: content
        };
    }

    let result = []
    result.push(item('Name', `${student['first-name']} ${student['last-name']}`));
    result.push(item('Grade', student['grade']))
    result.push(item('Height', `${student['height'] * 0.01}m`))
    result.push(item('Weight', `${student['weight']}kg`));
}

上面的 items 函數(shù)是不能復(fù)用的嚼酝,但是我們釋放了我們的列表視圖浮还,它現(xiàn)在和業(yè)務(wù)邏輯相解耦了。
如果你能理解上面的示例闽巩,現(xiàn)在你就明白 M-V-VM 是如何運(yùn)作的了钧舌。

M-V-VM

我們示例中的 items 函數(shù)就是一個 View-Model担汤。它將模型中返回的數(shù)據(jù)轉(zhuǎn)化成視圖可以接受的特殊結(jié)構(gòu)。現(xiàn)在洼冻,我們將發(fā)現(xiàn)我們之前忽略了的問題:
誰將負(fù)責(zé)處理指令崭歧?
比如,我們將添加一個按鈕來告訴系統(tǒng)學(xué)生信息是否是合法的:

<ul>
  <li v-for="item in items">
    <span>{{item.description}}</span>
    <span>{{item.content}}</span>
  </li>
</ul>
<button onclick="{{confirm}}">confirm</button>
<button onclick="{{reject}}">reject</button>

誰來實(shí)現(xiàn) confirmreject 函數(shù)呢撞牢?
視圖層調(diào)用了它們率碾,但是視圖層應(yīng)該和業(yè)務(wù)無關(guān)。我認(rèn)為我們應(yīng)該毫不猶豫地將其加入到 View-Model 層屋彪,View-Model 扮演了適配器的角色所宰,所以為啥不讓它作為一個雙向的適配器呢?
所以 M-V-VM 模式就像這樣:

M-V-VM

注意一些 M-V-VM 框架提供了雙向數(shù)據(jù)綁定(WPF畜挥,knockout.js……)仔粥,我認(rèn)為你使用雙向或單向綁定并不重要,重要的是蟹但,視圖層能通過一個適配器層同模型層交互躯泰,而如何綁定視圖層和 View-Model 層全憑你的個人喜好。于我而言华糖,帶指令的單向綁定是我的最愛麦向。

下一篇 >


原文地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市客叉,隨后出現(xiàn)的幾起案子诵竭,更是在濱河造成了極大的恐慌,老刑警劉巖兼搏,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件秀撇,死亡現(xiàn)場離奇詭異,居然都是意外死亡向族,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進(jìn)店門棠绘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來件相,“玉大人,你說我怎么就攤上這事氧苍∫勾#” “怎么了?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵让虐,是天一觀的道長紊撕。 經(jīng)常有香客問我,道長赡突,這世上最難降的妖魔是什么对扶? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任区赵,我火速辦了婚禮,結(jié)果婚禮上浪南,老公的妹妹穿的比我還像新娘笼才。我一直安慰自己,他們只是感情好络凿,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布骡送。 她就那樣靜靜地躺著,像睡著了一般絮记。 火紅的嫁衣襯著肌膚如雪摔踱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天怨愤,我揣著相機(jī)與錄音派敷,去河邊找鬼。 笑死憔四,一個胖子當(dāng)著我的面吹牛膀息,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播了赵,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼潜支,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了柿汛?” 一聲冷哼從身側(cè)響起冗酿,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎络断,沒想到半個月后裁替,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡貌笨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年弱判,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锥惋。...
    茶點(diǎn)故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡昌腰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出膀跌,到底是詐尸還是另有隱情遭商,我是刑警寧澤,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布捅伤,位于F島的核電站劫流,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜祠汇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一仍秤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧座哩,春花似錦徒扶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至屿良,卻和暖如春圈澈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背尘惧。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工康栈, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人喷橙。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓啥么,卻偏偏與公主長得像,于是被迫代替她去往敵國和親贰逾。 傳聞我的和親對象是個殘疾皇子悬荣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評論 2 355

推薦閱讀更多精彩內(nèi)容