由於瀏覽器與 JavaScript 標準的進展,近年來前端領域可以說是突飛猛進,各種前端框架/函式庫也如雨後春筍般紛紛出現。

常常有人問,「怎麼選擇前端框架」你會怎麼回答?

小孩子才做選擇,我全都要

既然有這麼多人願意開發這些工具,無論理由是什麼,就代表需求確實存在,問題需要被解決。


有句話是這樣說的:「每 18 至 24 個月,前端都會難一倍」

是變得簡單還是越來越難,我認爲要看團隊本身對於技術的掌握度以及專案的規模來決定。 如果要我用一句話來解釋,我會說前端框架/工具庫的發展方向,實際上是「在簡單的專案使用會變得複雜,在複雜專案的開發變得單純」。

談這個問題前,讓我們先來看看網頁技術的發展。 我相信,唯有了解過往的時空背景,我們才能看清現在與未來技術發展的脈絡。

網頁:毫無反應,就只是個文檔

HTML 最早是由網際網路 (World Wide Web) 之父 Tim Berners-Lee 所發明,而在 1991 年成為公開的文件規範。 而該規範並不是 HTML 1.0 ,而是稱為 HTML Tags ;當時的 HTML 主要是用來表達資料,支援的標籤也不多。

隨著時代的演進,雖然 HTML、JavaScript 與 CSS 都建立了標準,但是在網頁的領域依然 (相較於現在) 還是以後端語言為主。

我認為主要原因有兩個:其一是網路速度的限制,二是終端設備效能的低落。

網路速度我想不用我多做解釋了,回想一下就知道,3G、4G 的普及化也是最近沒幾年的事。 當網路的傳遞速度不夠快,我們也很難期待讓網頁可以成為一個成熟的應用平台對吧。

再來是終端設備的效能,這包含 PC 端上的效能、手機端的效能,甚至是瀏覽器的效能限制。 當硬體的效能還沒有達到一定水準,瀏覽器廠商也就沒有動力更新產品,而作為網頁應用的唯一載體,瀏覽器發展的停滯也就直接影響了整個前端技術的發展。 所以回顧過去,你會發現 ECMAScript 的標準更曾停滯了將近十年之久。

一直到最近幾年,這些限制才紛紛有了突破。

網頁如何變成應用程式

早期的網頁幾乎都是以靜態網頁為主,所有資料直接從 Server 端輸出,幾乎不需要做運算,就是單純的 HTML。 就算有後端語言,頂多也就是負責表單的處理,GET / POST 這些大家熟到不能再熟的東西。

後來過了幾年,有了「動態網頁程式」的語言 (如 PHP、ASP 等) 開始由後端程式語言負責處理頁面邏輯, 加上資料庫系統的成熟,使得原本無法紀錄狀態的網頁,可以利用資料庫來記錄狀態及資料。

既然提到網頁應用程式,就一定會提到 2004 年 Gmail 的誕生,Gmail 的橫空出世,同時也將 AJAX 這門技術發揚光大。

說實話,在當時 AJAX 它不能算是一個新技術,從字面上來說,「Asynchronous JavaScript and XML」 - 非同步的 JavaScript 與 XML, 就可以看出其實是好幾種技術的組合,而現今主流的 AJAX 甚至連 XML 都看不見了,多以 JSON 格式替代。 (所以應該改稱 AJAJ ?)

AJAX 的發展,讓開發者們發現原來不是只能透過瀏覽器發送 Request ,用 JavaScript 也可以向伺服器發送請求。 而這門技術事實上在 1998 年由微軟的 Outlook Web Access 小組所開發,叫做 XMLHTTP,但直到 2004 年 Gamil 的出現這門技術才廣為人們所知。

網頁開發者 (當時還未有前後端之分) 開始體會到,原來資料不只是透過單純後端操作,而是可以放在瀏覽器端來管理。 因此,網頁是可以用來開發「應用程式」的。

前端的任務

讓我們把主題拉回前端。 你認為前端的工作應該是什麼?

撰寫 HTML? CSS? JavaScript? 或許也有人會說要有美感、有設計的 sense、能跟後端溝通...

以上這些都沒錯。

但前端最關鍵的任務我會說是將資料與狀態視覺化

視覺化? 別開玩笑了,我知道講到視覺化很多人可能會想到 D3.js、EChart、highcharts 等等繪製圖表的工具,不過我們這裡要講的不是這個。

早期的前後端分工大多是這樣的,那時前端的工作基本上被戲稱為是「頁面仔」,簡單來說就是負責切版,寫寫 HTML、CSS,偶而再加點 JavaScript 特效。 然後就把頁面交給後端去套版,向後端拉拉資料顯示頁面就完工了。

那麼廣義來說,將 HTML 的原始碼透過瀏覽器轉化成使用者看得到的畫面,能不能算是「視覺化」的一種?

用 Bootstrap modal 、Lightbox 顯示對話框,即使在程式碼裡面只有 truefalse,但這個狀態決定對話框什麼時候出現,什麼時候消失,是不是也能算是視覺化的一種?

更深一層,當我們把網頁作為應用程式時,實際上我們就是在操作資料與狀態本身,那些標籤、元件都只是表皮,實際上我們就是把這些資料通通都「視覺化」,只是與傳統後端相比,前端除了要維護原本從後端取得的商業邏輯的狀態以外,還得多維護 UI 元件顯示邏輯的狀態。

你可能聽過 React 陣營提出了一個公式: view = f(state); 講的就是這件事。

當我們定義好畫面元素的顯示與操作邏輯後,剩下要關注的就只有狀態本身。 現代的幾個主流 MVVM 框架基本上也都遵循這個原則。

前端框架、函式庫的崛起

在理解了前端工程的主要任務與技術發展的時空背景後,總算要進入主題了。

作為前端開發控制網頁的手段,開發者第一個要面臨的問題就是 DOM 的操作。

在那個時期,各家瀏覽器對 JavaScript 的各種實作可以說是五花八門,其中最典型的例子就是網頁的「事件處理」:

在早期的 IE (其實也沒有那麼早,泛指 IE9 以前),事件綁定就分為 W3C DOM 標準的 addEventListener() 以及 IE 獨有的 attachEvent()

  function addEvent(ev, elem, func) {
    if (elem.addEventListener) {
      // W3C DOM
      elem.addEventListener(ev, func, false);
    }
    else if (elem.attachEvent) {
      // IE DOM
      elem.attachEvent("on" + ev, func);
    }
    else {
      // No much to do
      elem[ev] = func;
    }
  }

更不用說一個要加 on,另一個不用,如果同個事件綁定多個處理器,甚至連執行先後順序也不一樣。

而透過 jQuery 處理事件只需要一個 $(...).bind()$(...).on() 就足夠。

除了解決跨瀏覽器的問題之外,另一個痛點就是對於 DOM 的控制。

假設今天想要用 Javascript 讀取網頁中某個單選按鈕 (radio button),在沒有 document.querySelector 可以用的時代,來看看要怎麼做:

  var checkedValue = null;
  var elements = document.getElementsByTagName('input');

  for (var n = 0; n < elements.length; n++) {
    if (elements[n].type === "radio" &&
        elements[n].name == "radioField" &&
        elements[n].checked){
      checkedValue = elements[n].value;
    }
  }

像這樣,我們必須先找出所有的 <input> 標籤,然後透過 for 一個一個檢查它的 type 以及 name,最後才把對應的 value 取出。

透過 jQuery 來查找,我們甚至只需要一行:

  var checkValue = $('[name="radioField"]:checked').val();

就可以做到一樣的效果。 這也是為什麼 jQuery 在那個時代可以大放異彩,即便是現在也還有人在使用。 當然在這裡並不是要教各位怎麼使用 jQuery,而是想要表達一個觀念:「框架或工具都是因應某個需要被解決的問題而生」。

時代在進步,技術也在進步,在不同的時間點,就會有不同的問題需要被解決,技術總是圍繞著需求而生。

MV* 框架的出現與開發思維的轉變

在 jQuery 解決了 DOM 操作的問題後,首先面臨的就是狀態管理的問題。

我還記得我剛入行的時候,那時候沒有什麼狀態管理的觀念,後端把資料吐在 HTML 上,我們就透過 JavaScript 直接把資料從 DOM 取出,處理完再寫回 DOM 上。 像這樣把 DOM 當做是資料結構來處理資料的方式顯然有很大的問題,當某個 DOM 被刪除的時候,是否意味著這個資料將永久消失再也拿不到了?

所以,當時多數人會做的事情,就是把資料往某個全域變數丟。 優點就是彈性大,缺點就是彈性太大,什麼人都可以存取。

全域變數有什麼不好? 以學習的角度來說,在新手階段其實無需太在意別人怎麼說,當你遇到某個需求,你在第一時間想到的解法也許就是最棒的解法,做就對了。 如果這是個重要的專案,一定會有人盯著你做,換個角度來說,如果可以放手讓你自己來,那表示這個專案的重要性其實也還好。

直到你發現這個問題用這個解法會帶來其他難以解決的問題,那就表示專案的複雜度夠高了,那麼參考前人的作法,你會發現那些所謂的框架、工具、設計模式其實都是水到渠成的結果,這個時候你也升級了。

當然這個觀點放到技術選型也是一樣。


從早期的「義大利麵式程式碼」(Spaghetti code) 把所有的東西通通往 HTML 頁面塞,到了後來有人提倡「關注點分離」, 將 HTML、CSS 及 JavaScript 拆開來,這是表現層級上的關注點分離。

當專案的架構越來越大,人們開始把「關注點」從表現層移到了架構層面,於是又分成了「資料層」、「表現層」以及「邏輯層」,也就是所謂的 MVC 概念。

mvc示意圖

當瀏覽器開啟 View 的時候,View 會向 Controller 提出請求。 Controller 處理完商業邏輯的部分之後,要求 Model 端改變狀態,然後 Model 再將資料反映到 View 上。

早期像是 Backbone.js,算是我第一個接觸的前端 MVC 框架。

它搭配了 Underscore.js 及 jQuery,開發者可以透過 RESTful interface 來跟它的 Model 及 Collection 結合操作, 當資料被改變了之後,就會觸發 View 去更新,作出對應的變化,比較可惜的是當時還沒有實作 data-binding 的部分。

真正稱上集大成可以算是 EmberJS、AngularJS (2013) 以及 Knockout.js 時期,這幾套前端框架各有所長。 但最驚豔的部分是 View 和 Model 可以直接來做 binding,這對過去很習慣直接以操作 DOM 為開發手段的我們,其實是個相當大的突破。 除此之外,像是 directive、template 以及 routing 的整合,也為開發者帶來相當大的便利。

前面提到,以早期著名的前端 MVC 框架 Backbone.js 來說,使用者可以透過 DOM 事件對 View 發送指令,再由 View 要求 Model 改變狀態。 但同時使用者也可以透過操作 URL 來對 Controller 做操作,再由 Controller 去改變 View。

這個時候你會發現前端 MVC 與後端 MVC 有著一個很大的差異: 前端的 MVC 著重在事件流程,而後端的重點在於資料流。

後端 MVC 的 View 就相當於是前端的全部了: 前端與後端的 MVC 圖片來源:http://slides.com/evenchange4/2014-ntuim-prepconf#/2/3

當前後端分離後,後端幾乎不需要去處理前端 View 的部分。 相對地,後端的 V 變成前端的 M,那些原本放在後端處理畫面、邏輯的部分,有很大程度地往前端移動。

比起把原本後端的路由管理接回來用,此時前端更注重的是資料與狀態的管理,以及它們如何跟畫面的整合與同步。

於是前端框架慢慢發展成 MVVM 模式。

像早期的 Ember、Angular 採用資料的雙向綁定作法,當 View 操作 (DOM event) 變動,透過 ViewModel 去操作 Model (JavaScript Object),反過來也是。

後來崛起的 React、Vue 也都延續採用 Single Source of Truth (SSOT) 的準則,使得資料流更容易被追蹤,架構也更好維護。

一般來說,後端的 Controller 是指 URL 與 Http Method 的部分,而前端可以操作 URL (hashTag / history-api 的 pushState) 來處理路由,以及透過 DOM Event 去對 View 做操作。

所以現代主流前端框架不會直接限制自己為 MVC 框架,而是被稱作 MV* (MVC/MVP/MVVM...) 的框架。

MVVM 之後?

在眾多 M-V-VM 框架解決了狀態與畫面同步的問題後,下一個課題是網頁應用的元件化。 應用的元件化之後,代表同樣的東西可以重複拿來使用,觀念雖好,但也帶來了新的問題:元件與元件之間的狀態傳遞。

寫過 react 的朋友應該都知道,早期 react 官方其實不管這一塊,所以整個社群才會又出現 flux、reflux 到 redux 等等的解決方案,

如果不使用這些解決方案來管理狀態的話,當元件需要做狀態的傳遞,就得一口氣往上傳到最上層,然後再一步一步往下丟。

通俗點的比喻就像是大家庭裡面兩個堂兄弟講悄悄話,結果搞到阿公阿祖全家人都知道。

這個問題在 Vue 等其他框架其實也有,所以後來提出了 eventbus 的解決法。

Event Bus

寫過 Vue 的朋友都知道,這個 eventbus 跟前面講到的「全域變數」的概念相當類似,差別只在於它不像全域變數那樣自由,多了一層存取的限制。

如果我們說從前元件父子關係的狀態傳遞是樹狀結構,那麼 eventbus 就是網狀結構。 就好比某個元件想跟另一個元件溝通,先跟總機聯絡,這個總機會幫你轉到正確的目標,這個 eventbus 就是總機的角色。 在網站應用還在中小型規模的時候,用這樣的方式來管理狀態也是很合適的。

而當我們的專案規模成長到一定程度時,大到 eventbus 難以控制,那麼這個時候再考慮使用像是 Vuex、 Redux、MobX 甚至是 rxjs 之類的解決方案也不遲,就看你的需求複雜度與應用場景來決定採用什麼。

Vuex

結論

現代前端框架這麼多,我相信剛入行的朋友多數都會有選擇恐慌症,覺得我非得找到大家都說那個最強最好用的前端框架,再投入開發。

總覺得學了一個框架,結果這個框架在社群不是主流派別,豈不是虧大。 但綜觀前端技術的發展歷史,沿著時間軌跡看下來,其實不難發現,那些新的技術、新的工具都是圍繞著問題而生。 現有的工具不夠好用,綁手綁腳寫起來不夠爽,於是有人就自幹了一套新的解法,如果這個解法確實可以解決大家也遇到的問題,那麼竄紅起來也就只是時間的問題。

而那些所謂「過時」的技術或工具並不是說現在就不能用,而是隨著時間過去,開發者的想法提升了、技術的標準進步了,我們有更好的做法,過去的舊技術適合的場景變少了,於是慢慢退場。 上個世代的工具解決了上個世代的問題,然後不管是思維也好,技術也好,都在繼續向前推進。 就好像以前 jQuery 的 Deferred、現在 ES 標準的 promise、async/await... 過去的工具也許在未來會換個形式被納入變成標準也說不定。

所以 「為什麼需要前端框架?」「怎麼選擇前端框架?」

專案有它本身的複雜度,當然工具以及其生態圈也是。 如果 jQuery 甚至原生的 JavaScript 就可以解決你的需求,那麼也許你並不需要前端框架。

如果是學習的角度,我認為你可以隨便挑一套,先從小型、無長期維護需求的活動專案,或是自己的 side project 開始試著實作看看, 寫得下去就恭喜你,要是不合胃口就換一套,反正工具這麼多,總會試出一套適合自己的。

工商服務

這是我最近在五倍紅寶石開設的 VueJS 入門課程,如果你對 VueJS 有興趣,卻總是不得其門而入,歡迎點此 https://5xruby.tw/talks/vue-js/ 報名參加。