后MVC時代的前端架構(gòu)

很多人覺得,前后端的差異主要是分別承載了數(shù)據(jù)和樣式,功能和皮膚。前端就是視覺方面的,后端是實質(zhì)性的。追溯到很多年前,確實是這樣的,所謂的前端只是由于后端MVC中的View過于復(fù)雜,為了提升用戶體驗,提高加載速度,以及降低服務(wù)器壓力,所衍生出的一些優(yōu)化技術(shù)。

前端框架演進(jìn)

最初前端沒有架構(gòu),也不需要。但隨著UI交互的復(fù)雜度激增,我們發(fā)現(xiàn)API提供的數(shù)據(jù)仍然需要進(jìn)行處理,再進(jìn)行渲染,而分離這些需要處理的數(shù)據(jù)和視覺渲染部分后又需要一層進(jìn)行控制,自然而然,將后端的MVC照搬了過來。但其實人們并沒有發(fā)現(xiàn),并不是我們把MVC搬到了前端,而是把后端的V搬到前端之后又分成了MVC,這個問題暫且擱下,后面會解釋。

接著前端又再一次的變得復(fù)雜,尤其是不同交互對于同一資源的操作,導(dǎo)致過程化的控制器過于臃腫,而不堪重負(fù),MVVM應(yīng)運而生。人們都以為,MVVM就是把C變成了ViewModel,但是ViewModel是一個實體,如何控制呢?真正用會MVVM的開發(fā)者都會發(fā)現(xiàn),MVVM提倡的是定義而不是控制,定義M和V的關(guān)系,什么樣的Model應(yīng)該呈現(xiàn)什么樣的View,然后一切自然而然的隨著用戶的行為去改變。

其實原先的C被更高度的抽象了,變成了框架的一部分,讀取M和V的關(guān)系,并監(jiān)聽他們,在一方改變的時候根據(jù)關(guān)系修改另一方,達(dá)到雙向綁定。而ViewModel其實是為了描述這一關(guān)系而抽象出來的Model,因為它是相對于Model更偏向View的,所以叫ViewModel。接著出現(xiàn)的一系列MVWhatever很好的說明了ViewModel并不是C變的,人們以為既然C可以變成VM,那也可以變成別的。殊不知,ViewModel也是一種Model,它是由View分裂出來的,而View只能分裂出Model和View,不會出現(xiàn)別的。

樣式和數(shù)據(jù)的區(qū)別

React橫空出世,卻沒有人能說清楚它到底是MV什么,甚至許多人搞不清M在哪,最后干脆說React就是個View。那么React到底是什么架構(gòu)呢?先別急,我想先講一個??故事:

有一個語言學(xué)者提出一個觀點 - 法國人的數(shù)學(xué)很爛,為什么呢?因為法語中的quatre-vingt(80)的意思是 4個20,居然都不會把4乘以20計算出來,是不是數(shù)學(xué)很差?

第一次聽到這個故事,我感到很好笑,難道漢語就不是這樣了嗎?我們叫做“八十”,其實還是8個10,和4個20有什么區(qū)別,還不是都沒有計算。后來再一想,不僅不是,而且法國人數(shù)學(xué)應(yīng)該很好,因為漢語遵循了現(xiàn)代通用的10進(jìn)制,而法語是20進(jìn)制和10進(jìn)制混合的,所以他們大腦可以自然的映射10進(jìn)制和20進(jìn)制,就好比是左撇子為了迎合右撇子的世界而變得思維敏捷一樣的道理。

講到這里,可能你會覺得莫名其妙,這跟前端有什么關(guān)系。其實我只是想說明一個道理,我們經(jīng)常會被一瞬間的思考誤導(dǎo),比如這個故事中,人們會把習(xí)慣的“八十”看做是一個獨立的東西,而嘲笑法國人他們的“quatre-vingt”,其實我們只是習(xí)慣了這種計算,或者說在大腦中建立了映射,就忽略了計算,而將其看做一個整體。由此說明,我們在看到一個信息的時候是會進(jìn)行無意識計算的,從而你會認(rèn)為你看到的就是那個東西本身,并不需要計算。我們認(rèn)為‘八十’是一個對象,而不是表達(dá)式,但事實是沒有符號可以表達(dá)80這個數(shù),所以用‘八個十’的表達(dá)式來表示,只不過我們太熟練所以自動計算了。

拋棄固有思維,現(xiàn)在假設(shè)@為81進(jìn)制數(shù)的最大數(shù),既80,再假設(shè)有一種操作符可以把十進(jìn)制變成81進(jìn)制數(shù),這一過程是不是數(shù)據(jù)的計算?但如果有一種字體格式可以把連在一起的8和0兩個數(shù)字變成@的樣子,這算不算樣式的改變?也許你會覺得通過操作符是在計算機(jī)中當(dāng)做了@,而字體只是長得像,但計算機(jī)真正認(rèn)識的只有可能是二進(jìn)制binary,所以整個過程是不是也可以看做都是樣式?還有人會說,數(shù)據(jù)無論變成什么樣式它本身都不會變,而樣式會變。但如果顯示器有色差呢,你看到的藍(lán)色就真的是藍(lán)色嗎?樣式?jīng)]變,你看到的仍然會變。

其實數(shù)據(jù)和樣式本質(zhì)沒有區(qū)別,區(qū)別只在于程度,從計算機(jī)到人類的理解程度。差異來自于我們自己,我們把難以理解的叫數(shù)據(jù),容易理解的叫樣式,計算時間長的叫數(shù)據(jù),計算時間短的叫樣式。一個矩形你知道是樣式,一個長xx,寬xx,邊框為xx的東西你會以為是數(shù)據(jù),但對于程序來說其實是一樣的。處理一下,得到另一種形式的等同的東西,這就是一個不斷翻譯的過程,從二進(jìn)制翻譯到人類語言,甚至到非語言的一種印象,比如視覺,語音,甚至意識。無論是什么,都是讓人類更容易理解。

Component架構(gòu)

耗費了這么大的篇幅說明樣式和數(shù)據(jù)沒有區(qū)別的目的其實是為了解釋React的架構(gòu)。前面說到的MVC和MVVM其實都是對于View的一種演進(jìn),因為所謂View才是最復(fù)雜的,為什么說所謂呢,基于前面的結(jié)論,View就是Model,但它是人們難以理解的部分,所以沒有被抽象為Model,而是直接顯示出來讓用戶自己去讀。為了便于理解,想象一種極端的情況,Model只有二進(jìn)制形式,直接顯示給了用戶,理論上來說,用戶是可以看懂的,只是難度高了一點點。而從View抽象成Model的過程,其實就是程序?qū)⒍M(jìn)制翻譯出來的過程。而框架的所做的改進(jìn)就是翻譯程度的提升,讓用戶更容易的讀取。

回到React身上,它的架構(gòu)就十分清晰了,前面的MVC是把后端的View分離出了一部分Model,而MVVM,是把MVC的V又分離了一部分Model出來。React所做的猶如它的版本號一般,直接起飛,每個Component其實都是View分離出來的Model,理論上來說,你能抽象出無限層Component,這個極限上,View已經(jīng)簡單的沒有意義了。而實際來說,你可以視項目情況而定,把View抽象到某個程度后扔給用戶自己閱讀。而由于分成無數(shù)層,M到V的過程也變得簡單,不再需要控制,因為復(fù)雜的計算已經(jīng)被分解成了極簡單的計算分散到每一層中了,甚至有時候僅僅是Component為傳入的props加上一些字面量,然后傳入另一個Component,或者是分解或組合成Object再向下傳遞。一旦真的理解了View即是Model的思想,你就會發(fā)現(xiàn),React似乎什么也沒做,其實卻把什么都做了,而且非常簡單。

但Component這種架構(gòu)也有其問題所在,那就是太過于松散,對架構(gòu)設(shè)計的要求比較高。一旦你并非基于由機(jī)器到人類的理解程度來抽象分層你的Component,其復(fù)用性和擴(kuò)展性就會大大降低。

前端面向?qū)ο?/h2>

前面說到了View和Model沒有本質(zhì)上的區(qū)別,那么前端架構(gòu)和后端架構(gòu)為什么會有區(qū)別呢?原因很簡單,后端可以把未翻譯完的數(shù)據(jù)丟給前端,但前端不能隨隨便便丟給用戶,所以前端變成了多層MV,而不是后端簡單的分為了一層MV。前端面向?qū)ο蟮脑O(shè)計也更加的困難,尤其是擁有多年后端開發(fā)經(jīng)驗的開發(fā)人員,更容易誤導(dǎo)自己,因為在后端翻譯完成的東西,在前端就變成了最原始的東西。

舉個例子,就拿最常見的電商說事吧,設(shè)計一個商品頁面的Component架構(gòu)。在擁有所謂數(shù)據(jù)的時候,它是某一個商品的頁面,比如一件印著國旗的T恤。但顯然我們的代碼庫在運行之前是沒有這個從數(shù)據(jù)庫傳過來的數(shù)據(jù)的,所以我們沒法把它抽象成一個叫做NationalFlagTShirtPage的Component的。退而求其次,在失去后端數(shù)據(jù)之后,它應(yīng)該是一個衣服類的商品頁面,比如有一些尺碼對照和試衣功能,所以可以有一個叫做ClothesShowcase的Component。說到后端數(shù)據(jù)的這一層,并不是為了搞笑,它反映出了一個問題。其實后端對于前端,就是上一層的Component。同理,在下一層的Component中,我們也應(yīng)該忽視這一層傳入到下一層的數(shù)據(jù),因為它不應(yīng)該有這個數(shù)據(jù)。

比如ClothesPage還應(yīng)該包含一個顯示圖片的區(qū)域,和一個顯示尺碼信息的區(qū)域。這時,很多人會在ClothesShowcase的里面再放一個ClothesImage和ClothesInformation,但是在上一層作為代碼一部分的'Clothes',在這一層應(yīng)該已經(jīng)被忽略,我們應(yīng)該放入ProductImage和SizeInformation。只有當(dāng)ClothesPage調(diào)用SizeInformation并傳入'S','M','L'之類衣服尺碼作為參數(shù)的時候,它才是衣服尺寸,而它自身應(yīng)該僅僅是尺寸信息的Component。我們會發(fā)現(xiàn),上一層的代碼(字面量'L'等),變成了下一層的數(shù)據(jù),如此一直下去,所有的特性都會變成數(shù)據(jù),而代碼,可能僅僅是一些最基礎(chǔ)的元素,比如按鈕,方框之類的,甚至是HTML本身。

如果一直使用對于后端來說的數(shù)據(jù)層面的'clothes'的話,就只能一次把ClothesPage這個Component寫好,而沒法繼續(xù)抽象了。而其子Component可能僅僅是由于過于臃腫而強(qiáng)行分割的patial了。這樣做除了代碼短一些之外,完全不具有復(fù)用性和擴(kuò)展性,因為ClothesPage下的所有Component都只能為Clothes服務(wù)了。而其他每個概念都必須把這一切重復(fù)一遍。

如果走入另一種極端呢,抽象出一種萬用Component,只要傳入一個大而全的object,就可以渲染出任意的頁面。這個時候雖然復(fù)用性有了,但是你會發(fā)現(xiàn)你什么也沒做,因為這個Component就是HTML的另一層封裝,那些傳入的參數(shù)包含了所有數(shù)據(jù),你需要把這些不同的數(shù)據(jù)同化。

歸根結(jié)底,Component架構(gòu)的精髓在于多層,按照人類理解程度分層。否則永遠(yuǎn)無法分清楚什么是數(shù)據(jù),什么是樣式,因為它們只會在某一層中有劃分。混亂的分層只會導(dǎo)致架構(gòu)回歸到傳統(tǒng)的MVC兩層結(jié)構(gòu)中去。也因此,前端面向?qū)ο蟊仨毣谝粚樱撾x了某一層而論對象或類,都是可笑的,一個類,到了下一層可能就是一個實例。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容