先後端合做新模式

本文主要介紹一種全新的先後端合做模式,在介紹這種模式以前咋們先來看看先後端合做模式的演變。css

singsong:該模式主要對傳統多頁面應用構建的改進。關於 SPA(Single Page Application,單頁應用程序)能夠參考 SSR(Server-Side Render,服務端渲染)。html

演變

在那個前端角色比較弱化的年代,頁面主要以靜態頁面爲主。合做模式很簡單粗暴。前端

model-1

前端開發完頁面,輸出給後端。後端拿到頁面拼模板,而後再渲染輸出。但隨着前端業務複雜性逐漸地增長,這種模式合做起來就不是很愉快了。後端在拼模板時,不能確保輸出的頁面結構與前端保持一致。爲了規避這個問題,聰明的開發人員就給前端同窗建議:使用後端開發環境開發前端頁面node

model-2

前端直接在本地部署後端開發環境,只負責 views 開發。再基於代碼託管工具 git,實現先後端的合做。此時仍是之後端爲主導,維護性成本依然很高。webpack

隨着 node.js 的崛起,前端工程化也逐漸成爲前端開發重要組成部分。各類構建工具(如 webpack、rollup、grunt、gulp 等),MVVM 框架(如 React、Vue、Angular 等),模塊化系統(cjs、amd、umd、ES6-modules 等),CSS 預處理器(SASS、Less、Stylus、Postcss 等),模塊管理工具(NPM、Yarn、brower 等)猶如雨後春筍般不斷涌現。SAP 也開始在前端領域流行來,前端能作的事情更多。如客戶端渲染,靜態分析、優化打包等。後端只需負責數據提供。此時,先後端已徹底分離,各自負責各自的業務。git

model-3

這種合做模式的核心:客戶端渲染 + 接口。因爲基於客戶端渲染,對瀏覽器的 SEO 不是很友好。雖然能夠經過 SSR 來解決,可是 SSR 也存在侷限性。關於 SSR 感興趣的同窗可自行查閱相關的資料。本文主要探討傳統多頁面應用構建的優化。github

基於前端工程化,要讓傳統多頁面應用構建也支持先後端徹底分離。還須要作一件事,前端腳手架須要與後端使用相同的模板渲染引擎。前端編寫好後,直接輸出模板給後端使用。爲此本身也構建一個 fes 腳手架。web

model-4

在使用 fes 構建了幾個項目後,雖然在開發體驗、開發效率上都獲得了很大地提高。但與後端合做起來不是很輕鬆。由於在開發以前須要與後端約定好模板數據變量,通常會以文檔形式進行說明。若是後端更新了數據,沒同步更新文檔,就會存在數據不一致的問題。對開發效率大打折扣,與傳統的後端從新拼接模板相比優點不是很明顯。秉着 geek 的精神,就想能不能 將模板數據以接口的形式提供給前端。這樣上述問題不就迎刃而解了麼。gulp

model-5

因而就對 fes 進行改造。讓其支持模板數據接口的配置,在渲染以前會根據配置拉起接口數據,再進行渲染輸出。同時還提供了接口數據適配功能,能讓客戶端更好地控制數據結構。後端

mockConfig: {
  // 訪問路徑做爲key
  '/index': {
    // 提供渲染模板數據接口
    api: 'https://postman-echo.com/get?page=index',
    // 適配數據
    format: data => data.args,
  },
  '/fes/info': {
    api: 'https://postman-echo.com/get?page=info',
    format: data => data.args,
  },
}
複製代碼

另外,爲了方便查看模板數據,開發模式下輸入 mock 指令可獲取當前的模板數據。

$ mock
Mock Data:

{
    "/about": {
        "page": "about",
        "common": "commons",
        "data": "singsong",
        "name": "fes-about-page"
    },
    "/index": {
        "page": "index",
        "common": "commons",
        "name": "fes-index-page",
        "data": {
            "name": "fes"
        },
        "article": {
            "_value": {},
            "_state": 1
        }
    },
    "/post": {
        "common": "commons"
    }
}
複製代碼

若是須要查看對應頁面數據,只需輸入對應的路徑便可。如 /index

$ /index
Mock Data [/index]:

{
    "page": "index",
    "common": "commons",
    "name": "fes-index-page",
    "data": {
        "name": "fes"
    },
    "article": {
        "_value": {},
        "_state": 1
    }
}
複製代碼

除了 mock 指令,還提供了以下指令:

  • mock: 查看當前的 mock 數據
  • view: 打印範圍地址和二維碼信息
  • clear: 清空控制檯

這種合做模式,須要後端多作一件事。額外提供一個包含模板數據的接口供前端使用(只存在開發環境下,在上線時須要關閉掉)。只需對該類接口定義特定的前綴,而後在模板渲染邏輯以前攔截請求響應數據。這樣不只能保持數據一致性,並且維護起來也方便。

Demo

這裏以改進後 fes 進行演示。以下以 index 頁面 爲例進行講解:

前端修改

index 頁面路由爲:http://127.0.0.1:3001/index,結構以下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>{{ title }}</title>
  </head>
  <body>
    <h1>{{ title }}</h1>
  </body>
</html>
複製代碼

模板數據接口爲:http://127.0.0.1:3001/mockdata/index。其中/mockdata爲前綴,接口返回的數據以下:

{
  "rawdata": {
    "title": "index page"
  },
}
複製代碼

在 fes 中對模板數據接口進行以下配置

mockConfig: {
  // 訪問路徑做爲key
  '/index': {
    // 提供渲染模板數據接口
    api: 'http://127.0.0.1:3001/mockdata/index',
  }
}
複製代碼

這裏若是隻想要 rawdata 中的變量數據。能夠經過format 進行適配。最後的配置信息以下:

mockConfig: {
  // 訪問路徑做爲key
  '/index': {
    // 提供渲染模板數據接口
    api: 'http://127.0.0.1:3001/mockdata/index',
    // 適配數據
    format: data => data.rawdata,
  }
}
複製代碼

後端修改

  • 路由處理器

    //path: ./controllers/index.js
    module.exports = async ctx => {
      const {url} = ctx;
      // 從 DB 獲取數據
      const getDataFromDB = () => {
        // 作一些數據查詢操做……
        return {
          rawdata: {
            title: 'index page',
          },
        };
      };
      const data = getDataFromDB();
    
      // 判斷是否有前綴進行不一樣的響應
      // 這裏前綴已保存在 ctx.mockApiPrefix 中
      if (url.indexOf(ctx.mockApiPrefix) === 0) {
        ctx.body = data;
      } else {
        await ctx.render('index', data.rawdata);
      }
    };
    複製代碼
  • 定義路由

    // path: ./pages/index.js
    // 該接口與頁面請求使用相同的路由處理,經過是否有`PREFIX`進行區別
    module.exports = (PREFIX = '') => [
      {
        method: 'get',
        path: `${PREFIX}/index`,
        middleware: require('./controllers/index'),
      },
    ];
    複製代碼
  • 註冊路由

    const Router = require('koa-router');
    const router = new Router();
    
    const pagesConfig = require('./pages/index');
    
    const config = [];
    // 定義前綴
    const MOCK_API_PREFIX = '/mockdata';
    // 註冊頁面路由
    config.push(...pagesConfig());
    
    // 註冊模板數據接口
    if (process.env.NODE === 'dev') {
      config.push(...pagesConfig(MOCK_API_PREFIX));
    }
    
    const generateRoutes = (router, config) => {
      config.forEach(({method, path, middleware}) => {
        router[method](path, middleware);
      });
    };
    
    module.exports = app => (ctx, next) => {
      // 綁定前綴,方便後續邏輯使用
      ctx.mockApiPrefix = MOCK_API_PREFIX;
      generateRoutes(router, config);
      app.use(router.routes()).use(router.allowedMethods());
      return next();
    };
    複製代碼

運行效果:

總結

本文主要與你們分享一種先後端合做新模式,也許當成一種新思路更恰當些😀,由於它只是對多頁面應用構建的一種優化。無論怎樣也但願本文能讓各位讀者有所收穫。週末愉快~~~🌴

相關文章
相關標籤/搜索