iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 21
4
Modern Web

Half-Stack Developer 養成計畫系列 第 21

你走你的陽關道,我走我的獨木橋:前後端分離

你走你的陽關道,我走我的獨木橋:前後端分離

接續我們之前講的 ajax,自從有了 ajax 以後,就可以在任何時候從前端主動跟後端要資料。這代表什麼呢?這代表如果你想要的話,可以把前後端「整個切開」。

這是什麼意思呢?還記得我們之前提過的 MVC 架構嗎?一個 request 進來之後,透過 controller 處理,從 model 讀取資料,最後把東西全部傳到 view 以後回傳 response。請注意到這邊的這個 view,就是指前端的部分。在這個模式之下,前端跟後端是沒辦法「完全切開的」,你如果後端 server 壞了,就輸出不了任何頁面,整個網站都一起掛掉。前後端是綁在一起的。

那有了 ajax 以後要怎麼分開呢?這很簡單,後端變成純粹只輸出「資料」,注意喔,是「資料」,而不是「view」。以前你的某一個路由可能會輸出整個 html 檔案,可是完全切開之後,後端 server 只負責提供單純的資料,例如說:{status: 'SUCCESS', id: 100}之類的這種資料。

那前端呢?前端就是標準的 HTML + CSS + JavaScript 靜態頁面,完全不用依靠 server side 去 render,而是在 client side render。

我用文字打一打發現很難講的清楚,於是只好做了一張簡單的圖來說明一下我們之前的流程是什麼:

server.png

當你想要訪問文章列表這個頁面的時候,瀏覽器會送 request 到 server,然後經過 controller, model 最後把資料帶給 view,view 再回傳一個完整的 HTML 檔案傳回(這個動作就叫做 render),瀏覽器拿到一份完整的 HTML 檔案,只要顯示出來就好。因為 render 在 server side,所以叫做 server side render。

那 client side render 是什麼意思?

client.png

當你想要訪問文章列表的時候,你只是開啟一個靜態的 index.html 檔案,檔案裡面自己寫 JavaScript 的 code 用 ajax 去跟 server 拿資料。因為只是拿資料,所以 server 的 view 這一層已經不見了(根本用不到),當 client side 拿到資料以後,再自己寫程式碼用動態的方式 render 出頁面,這樣就叫做 client side render。

而後面這一種,你有沒有發現前後端已經完全切開了?如果後端 server 掛掉,你前端的頁面還是可以開,只是會拿不到資料,看不到文章而已。而且前後端切開的好處是後端變成純粹的 API server,你想要的話,任何一個 client 都可以來拿資料,而不是像之前那樣子把前端跟後端綁在一起。

這樣說你可能還是會覺得有一點不太懂他們的差別在哪裡,我寫一個簡單的範例給你看。我們一樣拿之前的那個留言板範例來用,但是要記得先把輸出文章的部分改成只輸出資料,而不是輸出 HTML。這邊為了方便起見,我們先把判斷管理員這個功能拿掉:

// 首頁,直接輸出所有留言
app.get('/', function (req, res) {

  // 拿出所有的留言
  db.getPosts(function (err, posts) {
    if (err) {
      res.send(err);
    } else {

      // 記得要把 posts 反過來,才是正確的順序
      // 直接把所有 posts 丟出去
      // 這個 header 有點小複雜,以後有空再講,有興趣的可以自己去查一下
      res.header('Access-Control-Allow-Origin', '*');
      res.send({
        posts: posts.reverse()
      });
    }
  })
});

接著打開http://localhost:3000,你應該就會看到順利的輸出了 JSON 格式的資料:

{"posts":[{"_id":"585f662a77467405888b3bbe","author":"huli","content":"2222","createTime":"2016-12-25T06:24:42.990Z"},{"_id":"585f662777467405888b3bbd","author":"huli","content":"1111","createTime":"2016-12-25T06:24:39.601Z"}]}

再來我們新建一個index.html的檔案,裡面負責發送 ajax 以及把收到的資料 render 成 html:

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" />
  <script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
  <script>
    $(document).ready(function() {
      getPosts();
    })

    // ajax 抓取文章
    function getPosts() {
      $.ajax({
        url: 'http://localhost:3000/',
        success: function(response) {
          if (!response.posts) {
            return alert('Error');
          }
          for(var i = 0; i < response.posts.length; i++) {

            // 丟給 render function
            addPost(response.posts[i]);
          }
        },
        error: function(err) {
          console.log(err);
          alert('抓取失敗');
        }
      })
    }

    function addPost(post) {

      var item = '' + 
        '<div class="panel panel-default">' +
          '<div class="panel-heading">' +
            '<h3 class="panel-title">' + post.author +', 發佈時間:' + post.createTime + '</h3>' +
          '</div>' +
          '<div class="panel-body">' +
            post.content
          '</div>' +
        '</div>';
      $('.posts').append(item);
    }
  </script>
</head>
<body>
  <div class="container">

    <a class="btn btn-primary" href="/posts">發表新留言</a>
    <h2>留言列表</h2>
    <div class="posts">
      
    </div>
  </div>
</body>
</html>

最後,直接把 index.html 點擊兩下打開,你就可以看到一個有所有文章的畫面了。如果你用右鍵 -> 檢查,會看到所有的元素都有出現:

ele1.png

可是如果你用:右鍵 -> 檢視原始碼,會發現 HTML 幾乎是沒有東西的:

ele2.png

這是為什麼呢?這個就是因為我們用的是 client side render。我們是在執行期間「動態」去跟後端伺服器拿資料,再「動態」生成你看到的那些元素,這些元素在 index.html 裡面本來就是不存在的,是我們後來才自己用 jQuery append 上去的。

你再回去看一下我前面那兩張圖,大概就能理解最大的差異到底在哪邊。一個是輸出「完整的 HTML」,另一個是輸出「完整的資料」,再用 JavaScript 自己生成「完整的 HTML」。前者叫做 server side render,後者就叫做 client side render,取決於你最後看到的 HTML 到底是哪邊產生的。

而如果你把這種概念發揮到極致,就會寫出一個 SPA,Single Page Application。

最經典的例子就是之前提過的 Gmail。你在用 Gmail 的時候,完全沒有換頁。全部的動作都是在「同一個頁面」上面發生的,所以你載入的檔案從頭到尾就只有一個 index.html,完全沒有換過。你在 Gmail 上面做的任何動作,都是用 ajax 發 request 給 server,server 回傳資料以後 client 端再用 JavaScript 把畫面 render 出來。

講到這邊,我要講一個你聽到之後應該會混淆的名詞:前端 MVC。

這個時候你應該要很疑惑:「前端 MVC 是三小?為什麼前端也有 MVC?」。沒關係,這跟我當初聽到這個時候的反應差不多。我們先來回憶一下我們剛剛改進過後的後端架構:

client.png

有沒有發現根本不需要 View 那一段了?因為你沒有 HTML 要輸出嘛,而且 render 也不是在 server side 做,所以根本不會用到 View 這一層。所以原本在後端的 View 這一層要處理的東西,就要全部搬過來前端做了。

意思是說,就連路由也要由前端自己來做。本來你造訪/posts就會輸出一個有所有 posts 的 HTML 檔,造訪/login就會有一個登入頁面出現,一切都相當合理。可是如果你的後端變成只輸出資料的話,該怎麼辦呢?所以路由也只好給前端自己做了。

前端可以靠window.location來知道現在的網址是什麼,只要監聽這個的變化然後去做相對應的事情就可以了。你有沒有覺得前端變得很複雜?你是不是本來以為前端就只是刻刻畫面,寫個 HTML, CSS,偶爾用到一些 JavaScript 而已?沒錯,以前是這樣,但現在不是了。當我們要做 SPA 的時候,一切都會變得超級無敵複雜,所以這一段你一定要聽懂脈絡,不然你之後會很痛苦不知道自己到底在幹嘛。

我們先來講講,到底什麼場合會需要用到 SPA 好了。最需要的一個場合是那種線上的音樂播放網站。為什麼?因為你必須一邊播放音樂,一邊讓他可以在網站上看其他的資料,例如說歌手介紹、專輯介紹之類的。如果你今天不是用 SPA,那使用者點到別的頁面的時候,瀏覽器就跳頁了,音樂就停了。哇靠這個體驗也太差了,完全不能接受。

所以這種網站一定要用 SPA,沒有其他選擇。用了之後因為不會跳頁,所以你點擊歌手介紹的時候,只是發一個 ajax request,然後收到 response 之後用 JavaScript 把接收到的資料 render 成 HTML 顯示出來。不管到哪一個頁面,都不會真的跳轉,不會載入新的檔案

第二個案例是像 Twitch 這種網站。他最近多了一個很不錯的功能,那就是左下角會有一個浮動的實況畫面,你可以邊看實況邊去逛其他的地方之類的。大概就像這樣:

twitch5.png

第三個案例就是經典的 Gmail,你在操作的時候不會覺得是在開一個網頁,而是比較像在用一個 App 的感覺。你在一個頁面上面就可以完成一切。

總之,會需要用到 Single Page Application 的場合有兩個,一個是因為必須要這樣做,另一個是因為可以增進使用者體驗,讓使用者覺得操作起來更順暢。

如果你發現你要做的東西不符合這兩種場合,你可以選擇不要做 SPA,可以選擇就依照之前那樣的 MVC 架構,由 server side 去 render、去處理。這一切都是可以選擇的。

好了,再讓我們回到前端 MVC 的話題,為什麼前端也需要一個 MVC 架構?因為 SPA 比你之前寫過的前端都複雜多了,他必須自己處理路由、頁面切換、資料抓取、狀態管理等等一堆有的沒的東西,所以會變得超級複雜。因此你需要有一個架構來讓這整件事情變得簡單一點。前端 MVC 跟後端 MVC 其實滿類似的,就是分成三個不同的角色管理不同的事情。這邊你最好奇的可能會是「Model」,前端的 Model 要幹嘛?你應該知道後端的 Model 就是去跟資料庫拿資料,然後傳回 Controller。其實前端的也是一樣,也是去拿資料?找誰拿?當然是找後端 API 拿。所以前端的 Model 也是在拿資料。可以看一下下面這張比較圖:

mvc.png

你會發現其實前後端做的事情都差不多,只是前端注重在 render 畫面,後端注重在輸出資料。還可以畫出這一張完整的流程圖:

all.png

用文字來解釋的話,流程是這樣的:

  1. 使用者造訪 /posts 這個網址,代表他想看全部文章
  2. 前端的路由去處理,負責呼叫對應到的 controller
  3. 前端 controller 去呼叫 Model 拿資料
  4. 前端 Model 透過 API,去 /api/posts 這個網址拿資料
  5. 後端路由接到 request,丟給對應到的後端 controller
  6. 後端 controller 跟後端 Model 拿資料
  7. 後端 controller 把資料傳回去
  8. 前端 Modle 拿到資料以後回傳給前端 controller,並且把資料丟給 view,由 view 回傳完整 HTML
  9. client side render,把畫面渲染出來

你上面看到的這整套,大概就是最基本的 SPA 的架構了。後端只負責輸出資料,前端來負責抓資料跟渲染畫面。把前後端完完全全的切開了,就算你後端壞掉,你前端還是看得到畫面(只是可能會顯示個錯誤畫面之類的);你前端壞掉,後端還是能安穩的輸出資料。兩邊乾乾淨淨,而且任何一邊都會比較好維護,前端工程師想要改任何跟介面有關的東西,都跟後端完全沒有關係,其實就等於是兩個不同的專案的意思。

講到 SPA,有幾套框架非常非常有名,第一個就是鼎鼎大名的 Angular,第二個則是比較不知名的 Ember(Twitch 就是用這個)。不過這兩個我其實都沒有用過,所以也不知道感覺如何,有興趣的可以自己去試著寫寫看。

最後再來強調一下看完這一章我希望你學到的東西:

  1. 為什麼要有前端 MVC
  2. 為什麼要有 SPA,我可以不寫 SPA 嗎?
  3. 前端 MVC 跟後端 MVC 的差別
  4. 我可以寫 SPA 但是不用任何現成框架嗎?
  5. 從你造訪某一個 SPA 頁面一直到看到畫面,這之間到底發生了哪些事?

如果你回答不太出來,這一篇裡面都有答案,你可以再去看看。如果有哪邊不懂得也都可以再留言發問,感謝。


上一篇
讓我們再轉 180 度,更即時的前端:ajax
下一篇
我也想要模組化開發:Webpack
系列文
Half-Stack Developer 養成計畫30

尚未有邦友留言

立即登入留言