跟著小明一起搞懂技術名詞:MVC、SPA 與 SSR

2018.05.24 by
Huli
Huli 查看更多文章

1994年生(雖然看起來不像),哲學系肄業,現職為在新加坡工作的前端工程師。喜歡教學,相信分享與交流可以讓世界更美好。

REDPIXEL.PL via Shutterstock
跟著小明一起搞懂技術名詞:MVC、SPA 與 SSR

這篇的靈感來自於Front-End Developers Taiwan裡面的一串討論,有人po了一個影片是來討論「MVC vs SPA」,這個標題一出來大家都驚呆了,想說怎麼會有這樣的比較,於是下面掀起一波激烈的討論,最後發現原po誤用了專有名詞,才導致這樣的結果。

雖然說很多技術名詞本來就是一個名詞各自表述,但基本概念通常都不會相差太遠,只有在細節上會有些許的爭議以及討論而已。

身為致力於要讓新手更容易搞懂技術名詞的人,不如就讓我來嘗試看看講解這幾個東西吧!

這篇文章的目標是:「只要你有網頁前端的基礎,就能夠搞懂我在說什麼」,如果你搞不懂的話,別擔心,不是你的錯,是我沒寫好。麻煩在下面留言一下讓我知道哪裡可以再改進。

接下來我會以主角小明為中心點出發,試著從一段虛擬的故事不斷帶出:「為什麼XXX會出現」、「為什麼我們需要XXX」這些問題。如果你只對真實歷史的名詞演進有興趣,那你可能要去維基百科才能找到比較正確的資料。本故事純屬虛構,如有雷同…應該不太可能會有雷同啦,就讓我們開始吧!

(先打個預防針,故事有點長,如果這些概念你都理解了應該會覺得這篇文章超級廢又超級長。)

第一幕:很久很久以前⋯⋯

小明是一個初學程式的新手,在這之前有用Dreamweaver寫過一些簡單的靜態網頁,對HTML、CSS以及JavaScript都有一些基礎,而朋友們都推薦他去學PHP來補足後端的部分。

經過了一個月的苦練之後,小明終於完成了他的第一個後端程式,是一個非常簡單的留言板系統(怕大家傷眼睛,這邊只截給大家看其中一部分)。

別笑,這就是你年輕時會寫出來的東西
胡立

PHP程式碼、商業邏輯、HTML,全部東西都混在一起做撒尿牛丸,寫了這樣的程式碼之後,小明每次考試都考一百分呢!

小明一開始覺得很興奮,自己終於能夠通曉前後端,成為全端工程師,便興沖沖的持續精進自己的後端技術,每天都加一個新的feature進去。過了兩個禮拜之後,小明整理了一下資料夾,發現總共有100個PHP檔案,每個檔案有超過300行code,而且全部都是PHP跟HTML混在一起寫。

他隨便點開其中一個檔案,看了10秒之後大喊:

我到底寫了三小!

意識到自己寫的code很爛,是邁向一個好的工程師的第一步。

第二幕:痛改前非

發現自己寫的程式碼連自己都看不懂的時候,小明覺得這樣不行,阿嶽也覺得不行,我也覺得不行。

因此呢,小明跑去十分瀑布下面打坐了三天三夜,不斷想著該怎麼樣讓他的程式碼變得更好,能夠更好維護、更好讀懂。他不求一步登天,只希望三個月之後當他回顧自己寫的code時,不要罵髒話就好。

就在第三天的晚上,他突然有了靈感,大喊了一聲:

Eureka!

就趕緊跑回家去重構自己的code了,而下面是他重構的結果:

純屬範例,絕對沒辦法跑
胡立

這個範例跟之前差在哪邊呢?

首先,他把任何跟資料有關的操作都放到一個叫做Model的地方去,所以你要改任何跟資料有關的東西,都到那邊就對了。

再來,他也把所有跟顯示畫面有關的東西都放到其他地方去,我們就叫做View吧,View裡面用一個template來塞入資料,不做任何跟資料有關的處理。

如此一來,他就把資料跟畫面顯示這兩者切開了,並且讓開頭那段 PHP code 把這兩者連接起來,先去Model拿資料,再把資料塞入View裡面輸出。那這個「連接兩者」的角色,就叫做Controller吧!

於是,MVC就這樣出現了。為的就是要把原來亂七八糟的程式碼理出一個頭緒來,是你的就是你的,不是你的就給我滾遠點。你要存取資料庫就是去Model裡面,你要寫HTML就去View,絕對不會出現在View裡面下SQL Query這種事情。

所以MVC是什麼?就是一種設計模式,你只要把你的code像這樣子切開,都可以叫做MVC,所以你不可能只看一個畫面就跟你朋友說:「欸欸,這個網站是MVC,因為他有好多個頁面」,除非你可以通靈看到背後程式碼的架構長什麼樣子。

話說後來又陸陸續續出現很多種模式,而且MVC其實也沒有想像中的職責這麼分明,在這邊我就不細講了,我自己對那整段歷史也沒有很熟,有興趣的可以參考:MVC是一個巨大誤會

然後我上面那段code是亂寫的,如果你對真實世界的MVC框架寫出來的code有興趣的話,會長成這樣:

PHP的框架CodeIgniter寫出來的
胡立

講到這裡我們做一個小總結,問自己三個問題:

  1. 為什麼要有MVC?
  2. 有MVC跟沒有MVC的差別在哪?
  3. 所以MVC是什麼?

三個問題可以一起回答:

因為小明寫的code太髒了太難維護,所以需要重構。而後來他發現用Model、View、Controller這三個概念來切的話可以把code寫得漂亮很多又好維護,就這樣做了。差別在於原本的code混在一起,遵守MVC的規範之後職責變得清楚很多。所以呢,MVC就是一種架構,後端可以遵守MVC的架構去開發,前端也可以,就算不是Web也可以用MVC。

第三幕:毛很多的使用者

小明把自己的爛code利用MVC模式重構之後,看起來還挺不賴的,至少比以前好很多,三個月過去了,也能看得懂自己以前寫的code。把留言板的程式碼重構得差不多之後,小明決定把這個專案公開,開放給大家註冊使用,讓每個人都可以有自己的留言板。

一開始狀況都還行,大家紛紛感激小明的無私奉獻,「祝樓主一生平安」、「感謝大大無私的分享」,可是好景不常,有天小明收到了一個回饋:

我每次留言之後頁面都會刷新,我家網速又慢,每次都要等個十幾秒,有沒有可能不要重新整理頁面?你看人家Gmail,我寄完信它也沒有重新整理啊!人家做得到你應該也做得到吧

身為一個濫好人,小明乖乖的去研究Gmail到底是如何做到的,發現秘訣就在於一個神奇的東西:Ajax,全名Asynchronous JavaScript and XML。

全名聽起來很嚇人,但說穿了其實就是你在JavaScript裡面可以非同步的去呼叫Server的API並且拿資料回來,在Ajax出現之前,你要把資料帶過去都必須透過Form的方式,一定要換頁。可是有了Ajax以後,不換頁也能跟Server溝通。

Gmail就是利用這樣的原理,才能達成寄信不換頁。

小明研究了一個假日之後便著手改造自己的留言板,把原本利用Form發送留言的地方變成Ajax,可是他碰到了一個問題:

原本我新增留言之後重新整理頁面就可以看到新的留言了,因為Server會把最新的結果傳回去;可是我現在用Ajax,我要怎麼在不刷新頁面的前提下在畫面上新增留言?

總而言之呢,利用Ajax之後的確是發了一個Request跟Server說你要新增留言,也成功了,可是畫面上不會平白無故就跑出一個新的留言。在經過短暫的思考後,小明得到一個很直覺的解法:「阿我就用JavaScript來新增就好啦!」

經過一番修改之後,新增留言的程式碼從原本很簡單的一個Form表單,變成下面這個樣子:

Ajax送出資料之後利用jQuery prepend上去
胡立

使用者的需求被解決了,小明也有了技術上的成長,可謂是一石二鳥、一舉兩得,但小明天真的地方就在於他把使用者想得太簡單了。

過了一個禮拜之後,同一個使用者又寫信給小明:

很感謝你上次新增的功能,可是我有個疑問。我看Gmail無論做什麼操作都不會換頁,你的留言板也可以改成這樣嗎?這樣比較方便,謝謝。

濫好人小明沒有仔細深究「比較方便」到底是怎樣的方便,純粹站在一個希望滿足使用者所有需求的角度跳下去研究Gmail到底還能夠做到什麼。

他發現了Gmail跟其他網站不同的地方就是:「無論做什麼操作都不會換頁」,換頁指的是「你會有一段時間看到整個畫面全白,因為瀏覽器正在等待Server的Response才能載入HTML」。

你在用Gmail的時候,無論你是寫信、讀信、整理信件或是切換到設定頁,儘管你的網路跟烏龜一樣,你還是看不到任何全白畫面。

為什麼?因為Gmail所有跟Server溝通的地方都是用Ajax。

這是改造前的範例,我們利用表單POST來新增一筆留言,所以你會看到一小段的白畫面:

此範例改自我學生Kris的作業
胡立

這是改造後的範例,因為我們用Ajax來新增留言的關係,所以你不會看到任何白畫面的出現,使用者體驗好很多:

利用Ajax跟JavaScript在前端新增留言
胡立

我們再舉一個簡單的小例子,假設小明今天寫了一個沒有用Ajax的Minmail,他刪除一封信的流程是這樣的:

  1. 點擊刪除之後,利用Form表單POST資料去/server/delete_email
  2. /server/delete_email處理完之後redirect回去信件列表
  3. 瀏覽器重新載入信件列表(在載入之前你都會看到全白畫面)

可是如果是像Gmail那樣子全部改成Ajax的話,就會變成:

  1. 點擊刪除之後,利用Ajax POST資料去/server/api/delete_email
  2. /server/api/delete_email處理完之後回傳Response
  3. 利用JavaScript在前端把那封信的從畫面上移除

後者利用Ajax跟後端同步資料,並且在前端用JavaScript更改畫面,所以你無論做什麼操作都不會換頁,也可以保證前後端的資料是同步的。

知道區別以及原理之後,小明把整個網站都改造成這種形式,只要是任何原本用到Form的地方,現在全部都用Ajax拿資料並搭配JavaScript來做畫面上的處理。

因此,留言列表現在變成Ajax拿資料回來之後由JavaScript把留言append到畫面上,就像我們剛剛示範的新增留言那樣。

此時,小明突然有個非常驚人的發現:

咦,如果我全部畫面都是由前端利用JavaScript動態產生的話,那我原本後端的View要幹嘛?

咦,對啊,既然現在所有畫面都是在前端由JavaScript動態產生,那我後端不就永遠都輸出同一個檔案就好?如此一來,使用者看到的其實都是同一個頁面,而我們利用JavaScript在這個頁面上做變化。

這個概念就叫做SPA,全名是Single Page Application,單頁式應用。與之對應的概念是MPA,Multiple Page Application。

SPA與MPA的對照
胡立

就如同小明領悟的一樣,前端如果利用SPA來實作的話,會把原本應該是後端處理的一部份職責給搬到前端去,例如說狀態的管理跟路由。

舉例來說,在以往Server根據不同的路徑對應到不同的Controller,進而渲染出不同的View。可是現在Server無論什麼路徑都會輸出同一個檔案,所以你在前端也要判斷現在的網址是哪個,才能決定在前端應該渲染出哪個畫面。

再舉一個例子,假設我現在寫了一個電影列表的網站,首頁列出許多熱門電影,點進去可以看到個別電影的詳細資料。而我們做了以下動作:

  1. 點進電影A
  2. 快速按上一頁
  3. 快速點進電影B

如果是SPA的話,實作的邏輯應該會是:「點進單獨電影時發送一個Request,等Response回來之後把資料顯示在畫面上」,乍聽之下沒什麼問題,但若是你在第三步的時候,第一步所發出去的電影A的Response才傳回來,你的畫面就會顯示出電影A的資訊,可是使用者點的明明就是電影B。

這就是我所說的狀態管理變複雜了,有些地方需要花點心思做處理。在以往MPA的時候完全不會發生這種事,你可以保證Server會回傳正確的結果,因為畫面是在後端render再回傳回來的,而且每一個頁面之間的狀態不會互相干擾。

如果寫得好,我相信SPA的使用者體驗一定很不錯,因為用起來就跟你在用Native App差不多嘛,但你必須付出的代價是前端變得超級複雜,有一堆非同步的問題要考慮還有一大堆事情要做。此時的前端複雜度已經跟我們最開頭示範的那種簡單留言板相差許多了。

在這種時候,前端也可以參考我們前面所說的MVC架構或是其他相關架構來讓程式碼的職責變得更分明,讓整個專案更好維護。所以你可以又有MVC又有SPA,或是沒有MVC但有SPA,這兩者是完全不同的概念。

我之前寫過另外一篇文章,有興趣的話可以參考看看:前後端分離與SPA

最後我舉一個一定要用SPA的例子:音樂播放網站。

如果音樂播放網站是用MPA的話,每去一個新的網址就會把整個頁面換掉,那你的網頁播放器就會中斷了,這是完全沒辦法接受的事。所以唯一的解法就是:播放器永遠都在頁面上,只有其他部分的內容換掉。而這一切都是在前端用JavaScript來處理的。

第四幕:行銷團隊的暴怒

小明花了整整一個月的時間不眠不休不吃不喝(誇飾法,開玩笑的),終於把整個網站改造成SPA,而且還優化了不少地方,讓整個使用者體驗變得非常非常好。

不久過後,這個留言板系統因為體驗實在是太好了,有越來越多人使用,短短一個月內就有了一百萬個來自世界各地的使用者註冊。還有來自國外的使用者甚至寫信給小明希望能夠付錢來擁有更多功能:

Hey, thanks for building such a cool website, I really like it. Is there any premium plan? I am glad to pay for the additional features like custom domain or custom template.

聽過一大堆創業講座的小明知道時候到了,可以把這個side project當作創業項目了!

憑著現有的成績,小明很快地就募到了天使輪,找了幾個夥伴成立了一間公司,想要把這個留言板系統做成全世界第一的留言板,期許自己能成為留言板界的WordPress。

可是好景不常,過了一兩個月之後不知道為什麼,新的會員越來越少,砸下大筆的廣告費也只帶來短暫的成效而已,一旦廣告停了就又恢復以往冷清的樣子。

奇怪,就算是熱潮退燒也沒退燒得這麼快才對,到底是發生什麼事呢?

一個禮拜過後,專長是數位行銷的合夥人氣噗噗的跑到小明的位子前,口氣很差地質問他:

你做了什麼?為什麼在搜尋引擎上面搜尋我們的網站,結果只會出現一大堆看不懂的程式碼?我們的網站SEO做的奇差無比你知道嗎?

小明一開始覺得很委屈,他什麼都沒做,怎麼會落得如此下場。但經過左思右想之後,終於發現了癥結點:SPA。

由於SPA是由前端的JavaScript動態產生內容,因此如果你對SPA的網站按下右鍵->檢視原始碼,只會看到空蕩蕩的一片,只看得到一個JavaScript檔案跟一些最基本的tag。

內容在哪裡?不在這裡,因為那是由JavaScript動態產生的。只有你的網站經由瀏覽器載入並且執行JavaScript,等Response回來之後才會動態產生出內容。因此無論是哪個頁面,你檢視原始碼都看不到動態新增後的內容。

慘了,這可是天大的壞消息。

但其實也沒有那麼壞,因為強大的Google的爬蟲其實支援執行JavaScript,所以他依然會index你在前端渲染之後的頁面。

不過還是有兩個問題,第一個是我們不知道Google如何執行,會不會前端還沒完全渲染完就已經爬完了?第二個是除了Google,還有其他很多搜尋引擎,有些可能沒有像Google這麼強大,碰到SPA就只能索引空蕩蕩的HTML,內容幾乎空白。

該怎麼辦呢?

苦惱的小明跟公司請了長假,再度跑到十分瀑布下面修行,希望能夠重演當年想出MVC架構的劇本。很幸運地,過了三天之後,小明終於想到解法了,大喊了一聲:

幹我知道了!

小明的想法是這樣的,既然問題出在「第一次渲染」,那我們只要在第一次渲染的時候把該輸出的資料都輸出就好啦,對使用者來說還是一個SPA,差別在於使用者接收到HTML的時候,就已經有完整的資料了。

舉例來說,假設使用者拜訪顯示所有留言的頁面,我在Server Side先把所有留言都準備好然後render出來,這樣使用者一收到Response的時候就能夠看到所有留言,搜尋引擎也能順利地爬到。

而後續的操作還是由JavaScript來處理,依舊能保持SPA的優點。或者我們能用一句話來總結:

第一個頁面由Server side render,之後的操作還是由Client side render

沒錯,這個概念就叫做SSR,Server Side Rendering。

CSR vs SSR
胡立

有了SSR以後,就解決了SEO的問題,對網路爬蟲來說你有沒有用SPA都無所謂,他所抓到的內容都是一樣的。可是對使用者來說,一樣能享受到SPA所帶來的好處(不用換頁)。

雖然我在這邊只用幾句話帶過去,看起來輕鬆寫意,但真的實作過的話你就會發現這不是一件容易的事,有很多細節要去考慮。總之呢,小明花了整整兩個月的時間才把整個網站都改成SSR。

不久後,在每個月的員工大會(新創嘛,一個月一次很合理)中CMO很開心地跟大家宣布產品在搜尋引擎上面的排名越來越高,自然流量也越來越多,註冊會員比起上個月增長了200%。

CMO很開心,搜尋引擎很開心,員工很開心,小明當然也很開心。

一天又平安的過去了,感謝飛天小明警的努力。

最終幕:前端的未來

因為科技進步快速加上網路的普及,世界變動的比以前快很多。

十年前手機還只是讓你打電話以及斤斤計較簡訊字數的工具,十年後就變成人手一台,不可或缺的小型電腦。

身為經歷過這一切的人,小明在深夜裡邊刷著leetcode邊回憶起前端的發展,遙想十年前他以為HTML跟CSS才是主角,JavaScript只是阻止使用者點右鍵或是做出會跟著鼠標移動的酷炫跑馬燈的小玩具。

可是一切發展的越來越快,jQuery的出現一統江湖,解決了惱人的跨瀏覽器問題,CSS也因為預處理器的出現而變得更好維護,可以用更程式化的角度來撰寫。再過個兩三年,大家都不談jQuery了,而是談Angular。可是又過了幾年,最潮的名詞變成React,到現在React也沒那麼潮了,要潮的話請去寫Vue。

更別提SPA的遍地開花以及SSR的出現,更是將前端的複雜度提升了不只一個檔次。有了SSR你就不再只是前端了,畢竟SSR的S可是Server的意思,還必須要會一點Server side的技術才行。

在Mobile的流量漸漸超越Desktop之後,前端的目標就邁向「可以逼近Native App」的體驗。又像是個App可是又不用安裝,那該有多好,省了安裝這個步驟轉換率大幅提升,使用者開心公司也開心。

於是大家開始提倡PWA,Progressive Web App。Web不再單純只是Web,而是要用起來像個App,看起來也像個App。甚至利用Service Worker搭配快取,在沒有網路時也能夠使用部分功能,也可以用Skeleton先把畫面的骨架顯示出來。

這一切的一切都為了一個目的:增進使用者體驗。

前端複雜歸複雜,但身為真心喜愛前端的人,小明可是對未來充滿了希望。一想到能夠接觸更多新的技術,更多新的解法,可以打造出更好的產品,小明內心湧起的情緒不是挫折而是興奮,無比的興奮。

東方的太陽緩緩升起,散射出的光芒灑在小明的房裡,提醒著他新的一天即將開始。

結語

對我來說,一個技術的出現絕對是有其理由的。而不是簡單一句:「前端現在就是這麼複雜」,我認為只要能理解他出現的脈絡,就能更輕易的從宏觀的角度去理解這項技術。

我們可以用三個問題來幫助自己理解一項事物:

  1. 為什麼要有XXX?
  2. 沒有XXX跟有XXX的區別是什麼?
  3. 所以XXX是什麼?

MVC就是因為code變得越來越亂,所以將職責區分清楚的一種設計模式。SPA就是因為想增進使用者體驗,而出現的一種在前端利用Ajax達成不換頁的方法。SSR就是因為要解決SPA的SEO問題而出現的解法。

一切都是有理由的,一切都是有原因的。你可以不懂它怎麼實作,但你一定要懂它是為了什麼而生。程式是工具,工具的目的是解決問題,重要的不是工具本身,而是背後要解決的那個問題。

感謝大家的閱讀,如果有任何錯誤麻煩不吝指出。另外,此篇文章只希望能給出一個大方向,對於細節如果要討論的話其實每個細節都可以再寫一篇專文,例如說MVC到底是在講哪個MVC?SPA在Google上的SEO真的比較差嗎?SSR在首次加載頁面上犧牲的時間(因為要等API的資料回來才能render)與增進SEO之間的取捨等等。

本文由Huli授權轉載自其Medium

《數位時代》長期徵稿,針對時事科技議題,需要您的獨特觀點,歡迎各類專業人士來稿一起交流。投稿請寄edit@bnext.com.tw,文長至少800字,請附上個人100字內簡介,文章若採用將經編輯潤飾,如需改標會與您討論。

(觀點文章呈現多元意見,不代表《數位時代》的立場。)

每日精選科技圈重要消息