很多人覺得,前後端的差異主要是分別承載了資料和樣式,功能和皮膚。前端就是視覺方面的,後端是實質性的。追溯到很多年前,確實是這樣的,所謂的前端只是由於後端MVC中的View過於複雜,為了提升使用者體驗,提高載入速度,以及降低伺服器壓力,所衍生出的一些優化技術。
<!– more –>
前端框架演進
最初前端沒有架構,也不需要。但隨著UI互動的複雜度激增,我們發現API提供的資料仍然需要進行處理,再進行渲染,而分離這些需要處理的資料和視覺渲染部分後又需要一層進行控制,自然而然,將後端的MVC照搬了過來。但其實人們並沒有發現,並不是我們把MVC搬到了前端,而是把後端的V搬到前端之後又分成了MVC,這個問題暫且擱下,後面會解釋。
接著前端又再一次的變得複雜,尤其是不同互動對於同一資源的操作,導致過程化的控制器過於臃腫,而不堪重負,MVVM應運而生。人們都以為,MVVM就是把C變成了ViewModel,但是ViewModel是一個實體,如何控制呢?真正用會MVVM的開發者都會發現,MVVM提倡的是定義而不是控制,定義M和V的關係,什麼樣的Model應該呈現什麼樣的View,然後一切自然而然的隨著使用者的行為去改變。
其實原先的C被更高度的抽象了,變成了框架的一部分,讀取M和V的關係,並監聽他們,在一方改變的時候根據關係修改另一方,達到雙向繫結。而ViewModel其實是為了描述這一關係而抽象出來的Model,因為它是相對於Model更偏向View的,所以叫ViewModel。接著出現的一系列MVWhatever很好的說明了ViewModel並不是C變的,人們以為既然C可以變成VM,那也可以變成別的。殊不知,ViewModel也是一種Model,它是由View分裂出來的,而View只能分裂出Model和View,不會出現別的。
樣式和資料的區別
React橫空出世,卻沒有人能說清楚它到底是MV什麼,甚至許多人搞不清M在哪,最後乾脆說React就是個View。那麼React到底是什麼架構呢?先別急,我想先講一個故事:
有一個語言學者提出一個觀點 – 法國人的數學很爛,為什麼呢?因為法語中的quatre-vingt(80)的意思是 4個20,居然都不會把4乘以20計算出來,是不是數學很差?
第一次聽到這個故事,我感到很好笑,難道漢語就不是這樣了嗎?我們叫做“八十”,其實還是8個10,和4個20有什麼區別,還不是都沒有計算。後來再一想,不僅不是,而且法國人數學應該很好,因為漢語遵循了現代通用的10進位制,而法語是20進位制和10進位制混合的,所以他們大腦可以自然的對映10進位制和20進位制,就好比是左撇子為了迎合右撇子的世界而變得思維敏捷一樣的道理。
講到這裡,可能你會覺得莫名其妙,這跟前端有什麼關係。其實我只是想說明一個道理,我們經常會被一瞬間的思考誤導,比如這個故事中,人們會把習慣的“八十”看做是一個獨立的東西,而嘲笑法國人他們的“quatre-vingt”,其實我們只是習慣了這種計算,或者說在大腦中建立了對映,就忽略了計算,而將其看做一個整體。由此說明,我們在看到一個資訊的時候是會進行無意識計算的,從而你會認為你看到的就是那個東西本身,並不需要計算。我們認為‘八十’是一個物件,而不是表示式,但事實是沒有符號可以表達80這個數,所以用‘八個十’的表示式來表示,只不過我們太熟練所以自動計算了。
拋棄固有思維,現在假設@為81進位制數的最大數,既80,再假設有一種操作符可以把十進位制變成81進位制數,這一過程是不是資料的計算?但如果有一種字型格式可以把連在一起的8和0兩個數字變成@的樣子,這算不算樣式的改變?也許你會覺得通過操作符是在計算機中當做了@,而字型只是長得像,但計算機真正認識的只有可能是二進位制binary,所以整個過程是不是也可以看做都是樣式?還有人會說,資料無論變成什麼樣式它本身都不會變,而樣式會變。但如果顯示器有色差呢,你看到的藍色就真的是藍色嗎?樣式沒變,你看到的仍然會變。
其實資料和樣式本質沒有區別,區別只在於程度,從計算機到人類的理解程度。差異來自於我們自己,我們把難以理解的叫資料,容易理解的叫樣式,計算時間長的叫資料,計算時間短的叫樣式。一個矩形你知道是樣式,一個長xx,寬xx,邊框為xx的東西你會以為是資料,但對於程式來說其實是一樣的。處理一下,得到另一種形式的等同的東西,這就是一個不斷翻譯的過程,從二進位制翻譯到人類語言,甚至到非語言的一種印象,比如視覺,語音,甚至意識。無論是什麼,都是讓人類更容易理解。
Component架構
耗費了這麼大的篇幅說明樣式和資料沒有區別的目的其實是為了解釋React的架構。前面說到的MVC和MVVM其實都是對於View的一種演進,因為所謂View才是最複雜的,為什麼說所謂呢,基於前面的結論,View就是Model,但它是人們難以理解的部分,所以沒有被抽象為Model,而是直接顯示出來讓使用者自己去讀。為了便於理解,想象一種極端的情況,Model只有二進位制形式,直接顯示給了使用者,理論上來說,使用者是可以看懂的,只是難度高了一點點。而從View抽象成Model的過程,其實就是程式將二進位制翻譯出來的過程。而框架的所做的改進就是翻譯程度的提升,讓使用者更容易的讀取。
回到React身上,它的架構就十分清晰了,前面的MVC是把後端的View分離出了一部分Model,而MVVM,是把MVC的V又分離了一部分Model出來。React所做的猶如它的版本號一般,直接起飛,每個Component其實都是View分離出來的Model,理論上來說,你能抽象出無限層Component,這個極限上,View已經簡單的沒有意義了。而實際來說,你可以視專案情況而定,把View抽象到某個程度後扔給使用者自己閱讀。而由於分成無數層,M到V的過程也變得簡單,不再需要控制,因為複雜的計算已經被分解成了極簡單的計算分散到每一層中了,甚至有時候僅僅是Component為傳入的props加上一些字面量,然後傳入另一個Component,或者是分解或組合成Object再向下傳遞。一旦真的理解了View即是Model的思想,你就會發現,React似乎什麼也沒做,其實卻把什麼都做了,而且非常簡單。
但Component這種架構也有其問題所在,那就是太過於鬆散,對架構設計的要求比較高。一旦你並非基於由機器到人類的理解程度來抽象分層你的Component,其複用性和擴充套件性就會大大降低。
前端物件導向
前面說到了View和Model沒有本質上的區別,那麼前端架構和後端架構為什麼會有區別呢?原因很簡單,後端可以把未翻譯完的資料丟給前端,但前端不能隨隨便便丟給使用者,所以前端變成了多層MV,而不是後端簡單的分為了一層MV。前端物件導向的設計也更加的困難,尤其是擁有多年後端開發經驗的開發人員,更容易誤導自己,因為在後端翻譯完成的東西,在前端就變成了最原始的東西。
舉個例子,就拿最常見的電商說事吧,設計一個商品頁面的Component架構。在擁有所謂資料的時候,它是某一個商品的頁面,比如一件印著國旗的T恤。但顯然我們的程式碼庫在執行之前是沒有這個從資料庫傳過來的資料的,所以我們沒法把它抽象成一個叫做NationalFlagTShirtPage的Component的。退而求其次,在失去後端資料之後,它應該是一個衣服類的商品頁面,比如有一些尺碼對照和試衣功能,所以可以有一個叫做ClothesShowcase的Component。說到後端資料的這一層,並不是為了搞笑,它反映出了一個問題。其實後端對於前端,就是上一層的Component。同理,在下一層的Component中,我們也應該忽視這一層傳入到下一層的資料,因為它不應該有這個資料。
比如ClothesPage還應該包含一個顯示圖片的區域,和一個顯示尺碼資訊的區域。這時,很多人會在ClothesShowcase的裡面再放一個ClothesImage和ClothesInformation,但是在上一層作為程式碼一部分的`Clothes`,在這一層應該已經被忽略,我們應該放入ProductImage和SizeInformation。只有當ClothesPage呼叫SizeInformation並傳入`S`,`M`,`L`之類衣服尺碼作為引數的時候,它才是衣服尺寸,而它自身應該僅僅是尺寸資訊的Component。我們會發現,上一層的程式碼(字面量`L`等),變成了下一層的資料,如此一直下去,所有的特性都會變成資料,而程式碼,可能僅僅是一些最基礎的元素,比如按鈕,方框之類的,甚至是HTML本身。
如果一直使用對於後端來說的資料層面的`clothes`的話,就只能一次把ClothesPage這個Component寫好,而沒法繼續抽象了。而其子Component可能僅僅是由於過於臃腫而強行分割的patial了。這樣做除了程式碼短一些之外,完全不具有複用性和擴充套件性,因為ClothesPage下的所有Component都只能為Clothes服務了。而其他每個概念都必須把這一切重複一遍。
如果走入另一種極端呢,抽象出一種萬用Component,只要傳入一個大而全的object,就可以渲染出任意的頁面。這個時候雖然複用性有了,但是你會發現你什麼也沒做,因為這個Component就是HTML的另一層封裝,那些傳入的引數包含了所有資料,你需要把這些不同的資料同化。
歸根結底,Component架構的精髓在於多層,按照人類理解程度分層。否則永遠無法分清楚什麼是資料,什麼是樣式,因為它們只會在某一層中有劃分。混亂的分層只會導致架構迴歸到傳統的MVC兩層結構中去。也因此,前端物件導向必須基於一層,脫離了某一層而論物件或類,都是可笑的,一個類,到了下一層可能就是一個例項。