基於vue-ssr服務端渲染入門詳解

wqzwh發表於2018-01-08

第一部分 基本介紹

1、前言

服務端渲染實現原理機制:在服務端拿資料進行解析渲染,直接生成html片段返回給前端。然後前端可以通過解析後端返回的html片段到前端頁面,大致有以下兩種形式:

1、伺服器通過模版引擎直接渲染整個頁面,例如java後端的vm模版引擎,php後端的smarty模版引擎。

2、服務渲染生成html程式碼塊, 前端通過AJAX獲取然後使用js動態新增。

2、服務端渲染的優劣

服務端渲染能夠解決兩大問題:

1、seo問題,有利於搜尋引擎蜘蛛抓取網站內容,利於網站的收錄和排名。

2、首屏載入過慢問題,例如現在成熟的SPA專案中,開啟首頁需要載入很多資源,通過服務端渲染可以加速首屏渲染。

同樣服務端渲染也會有弊端,主要是根據自己的業務場景來選擇適合方式,由於服務端渲染前端頁面,必將會給伺服器增加壓力。

3、SSR的實現原理

客戶端請求伺服器,伺服器根據請求地址獲得匹配的元件,在呼叫匹配到的元件返回 Promise (官方是preFetch方法)來將需要的資料拿到。最後再通過

<script>window.__initial_state=data</script>
複製程式碼

將其寫入網頁,最後將服務端渲染好的網頁返回回去。

接下來客戶端會將vuex將寫入的 initial_state 替換為當前的全域性狀態樹,再用這個狀態樹去檢查服務端渲染好的資料有沒有問題。遇到沒被服務端渲染的元件,再去發非同步請求拿資料。說白了就是一個類似React的 shouldComponentUpdate 的Diff操作。

Vue2使用的是單向資料流,用了它,就可以通過 SSR 返回唯一一個全域性狀態, 並確認某個元件是否已經SSR過了。

4、vue後端渲染主要外掛:vue-server-renderer

由於virtual dom的引入,使得vue的服務端渲染成為了可能,下面是官方 vue-server-renderer提供的渲染流程圖:

官方流程圖

可以看出vue的後端渲染分三個部分組成:頁面的原始碼(source),node層的渲染部分和瀏覽器端的渲染部分。

source分為兩種entry point,一個是前端頁面的入口client entry,主要是例項化Vue物件,將其掛載到頁面中;另外一個是後端渲染服務入口server entry,主要是控服務端渲染模組回撥,返回一個Promise物件,最終返回一個Vue物件(經過測試,直接返回Vue物件也是可以的);

前面的source部分就是業務開發的程式碼,開發完成之後通過 webpack 進行構建,生成對應的bundle,這裡不再贅述client bundle,就是一個可在瀏覽器端執行的打包檔案;這裡說下server bundle, vue2提供 vue-server-renderer模組,模組可以提供兩種render: rendererer/bundleRenderer ,下面分別介紹下這兩種render。

renderer接收一個vue物件 ,然後進行渲染,這種對於簡單的vue物件,可以這麼去做,但是對於複雜的專案,如果使用這種直接require一個vue物件,這個對於服務端程式碼的結構和邏輯都不太友好,首先模組的狀態會一直延續在每個請求渲染請求,我們需要去管理和避免這次渲染請求的狀態影響到後面的請求,因此vue-server-renderer提供了另外一種渲染模式,通過一個 bundleRenderer去做渲染。

bundleRenderer是較為複雜專案進行服務端渲染官方推薦的方式,通過webpack以server entry按照一定的要求打包生成一個 server-bundle,它相當於一個可以給服務端用的app的打包壓縮檔案,每一次呼叫都會重新初始化 vue物件,保證了每次請求都是獨立的,對於開發者來說,只需要專注於當前業務就可以,不用為服務端渲染開發更多的邏輯程式碼。 renderer生成完成之後,都存在兩個介面,分別是renderToString和renderToStream,一個是一次性將頁面渲染成字串檔案,另外一個是流式渲染,適用於支援流的web伺服器,可以是請求服務的速度更快。

第二部分 從零開始搭建

1、前言

上一節我們大致講了為什麼需要使用vue後端渲染,以及vue後端渲染的基本原理,這節內容我們將從零開始搭建屬於自己的vue後端渲染腳手架,當然不能不參考官方頁面響應的例項vue-hackernews-2.0,從零開始搭建專案,原始碼在將在下節與大家共享。

2、前期準備

基本環境要求:node版本6.10.1以上,npm版本3.10.10以上,本機環境是這樣的,建議升級到官方最新版本。

使用的技術棧:

1、vue 2.4.2
2、vuex 2.3.1
3、vue-router 2.7.0
4、vue-server-renderer 2.4.2
5、express 4.15.4
6、axios 0.16.2
7、qs 6.5.0
8、q https://github.com/kriskowal/q.git
9、webpack 3.5.0
10、mockjs 1.0.1-beta3
11、babel 相關外掛
複製程式碼

以上是主要是用的技術棧,在構建過程中會是用相應的外掛依賴包來配合進行壓縮打包,以下是npm init後package.json檔案所要新增的依賴包。

"dependencies": {
    "axios": "^0.16.2",
    "es6-promise": "^4.1.1",
    "express": "^4.15.4",
    "lodash": "^4.17.4",
    "q": "git+https://github.com/kriskowal/q.git",
    "qs": "^6.5.0",
    "vue": "^2.4.2",
    "vue-router": "^2.7.0",
    "vue-server-renderer": "^2.4.2",
    "vuex": "^2.3.1"
  },
  "devDependencies": {
    "autoprefixer": "^7.1.2",
    "babel-core": "^6.25.0",
    "babel-loader": "^7.1.1",
    "babel-plugin-syntax-dynamic-import": "^6.18.0",
    "babel-plugin-transform-runtime": "^6.22.0",
    "babel-preset-env": "^1.6.0",
    "babel-preset-stage-2": "^6.22.0",
    "compression": "^1.7.1",
    "cross-env": "^5.0.5",
    "css-loader": "^0.28.4",
    "extract-text-webpack-plugin": "^3.0.0",
    "file-loader": "^0.11.2",
    "friendly-errors-webpack-plugin": "^1.6.1",
    "glob": "^7.1.2",
    "less": "^2.7.2",
    "less-loader": "^2.2.3",
    "lru-cache": "^4.1.1",
    "mockjs": "^1.0.1-beta3",
    "style-loader": "^0.19.0",
    "sw-precache-webpack-plugin": "^0.11.4",
    "url-loader": "^0.5.9",
    "vue-loader": "^13.0.4",
    "vue-style-loader": "^3.0.3",
    "vue-template-compiler": "^2.4.2",
    "vuex-router-sync": "^4.2.0",
    "webpack": "^3.5.0",
    "webpack-dev-middleware": "^1.12.0",
    "webpack-hot-middleware": "^2.18.2",
    "webpack-merge": "^4.1.0",
    "webpack-node-externals": "^1.6.0"
  }	
複製程式碼

3、專案主目錄搭建

基本目錄結構如下:

├── LICENSE
├── README.md
├── build
│   ├── setup-dev-server.js
│   ├── vue-loader.config.js
│   ├── webpack.base.config.js
│   ├── webpack.client.config.js
│   └── webpack.server.config.js
├── log
│   ├── err.log
│   └── out.log
├── package.json
├── pmlog.json
├── server.js
└── src
    ├── App.vue
    ├── app.js
    ├── assets
    │   ├── images
    │   ├── style
    │   │   └── css.less
    │   └── views
    │       └── index.css
    ├── components
    │   ├── Banner.vue
    │   ├── BottomNav.vue
    │   ├── FloorOne.vue
    │   └── Header.vue
    ├── entry-client.js
    ├── entry-server.js
    ├── index.template.html
    ├── public
    │   ├── conf.js
    │   └── utils
    │       ├── api.js
    │       └── confUtils.js
    ├── router
    │   └── index.js
    ├── static
    │   ├── img
    │   │   └── favicon.ico
    │   └── js
    │       └── flexible.js
    ├── store
    │   ├── actions.js
    │   ├── getters.js
    │   ├── index.js
    │   ├── modules
    │   │   └── Home.js
    │   ├── mutationtypes.js
    │   └── state.js
    └── views
        └── index
            ├── conf.js
            ├── index.vue
            ├── mock.js
            └── service.js  
複製程式碼

檔案目錄基本介紹:

  • views資料夾下分模組檔案,模組檔案下下又分模組本身的.vue檔案(模版檔案),index.js檔案(後臺資料互動檔案),mock.js(本模組的mock假資料),conf.js(配置本模組一些引數,請求路徑,模組名稱等資訊)
  • components 公共元件資料夾
  • router 主要存放前端路由配置檔案,寫法規範按照vue-router官方例子即可。
  • store 主要是存放共享狀態檔案,裡面包含action.js,getter.js,mutationtype.js等,後期會根據模組再細分這些。
  • public 主要存放公共元件程式碼和專案使用的公共檔案程式碼,例如後期我們將axios封裝成公共的api庫檔案等等
  • static資料夾代表靜態檔案,不會被webpack打包的
  • app.js 是專案入口檔案
  • App.vue 是專案入口檔案
  • entry-client和entry-server分別是客戶端入口檔案和服務端的入口檔案
  • index.template.html是整個專案的模版檔案

開始編寫app.js專案入口程式碼

使用vue開發專案入口檔案一般都會如下寫法:

import Vue from 'vue';
import App from './index.vue';
import router from './router'
import store from './store';

new Vue({
	el: '#app',
	store,
	router,
	render: (h) => h(App)
});
複製程式碼

這種寫法是程式共享一個vue例項,但是在後端渲染中很容易導致交叉請求狀態汙染,導致資料流被汙染了。

所以,避免狀態單例,我們不應該直接建立一個應用程式例項,而是應該暴露一個可以重複執行的工廠函式,為每個請求建立新的應用程式例項,同樣router和store入口檔案也需要重新建立一個例項。

為了配合webpack動態載入路由配置,這裡會改寫常規路由引入寫法,這樣可以根據路由路徑來判斷載入相應的元件程式碼:

import Home from '../views/index/index.vue'
// 改寫成
component: () => ('../views/index/index.vue')
複製程式碼

以下是路由的基本寫法router,對外會丟擲一個createRouter方法來建立一個新的路由例項:

import Vue from 'vue'
import Router from 'vue-router';
Vue.use(Router)
export function createRouter() {
    return new Router({
        mode: 'history',
        routes: [{
            name:'Home',
            path: '/',
            component: () =>
                import ('../views/index/index.vue')
        }]
    })
}
複製程式碼

以下是store狀態管理的基本寫法,對外暴露了一個createStore方法,方便每次訪問建立一個新的例項:

// store.js
import Vue from 'vue'
import Vuex from 'vuex'
import * as actions from './actions'
import getters from './getters'
import modules from './modules/index'
Vue.use(Vuex)
export function createStore() {
  return new Vuex.Store({
    actions,
    getters,
    modules,
    strict: false
  })
}
複製程式碼

結合寫好的router和store入口檔案程式碼來編寫整個專案的入口檔案app.js程式碼內容,同樣最終也會對外暴露一個createApp方法,在每次建立app的時候保證router,store,app都是新建立的例項,這裡還引入了一個vue路由外掛vuex-router-sync,主要作用是同步路由狀態(route state)到 store,以下是app.js完整程式碼:

import Vue from 'vue'
import App from './App.vue'
import { createRouter } from './router'
import { createStore } from './store'
import { sync } from 'vuex-router-sync'
require('./assets/style/css.less');
export function createApp () {
  // 建立 router 和 store 例項
  const router = createRouter()
  const store = createStore()
  // 同步路由狀態(route state)到 store
  sync(store, router)
  // 建立應用程式例項,將 router 和 store 注入
  const app = new Vue({
    router,
    store,
    render: h => h(App)
  })
  // 暴露 app, router 和 store。
  return { app, router, store }
}
複製程式碼

entry-client.js程式碼編寫:

首頁引入從app檔案中暴露出來的createApp方法,在每次呼叫客戶端的時候,重新建立一個新的app,router,store,部分程式碼如下:

import { createApp } from './app'
const { app, router, store } = createApp()
複製程式碼

這裡我們會使用到onReady方法,此方法通常用於等待非同步的導航鉤子完成,比如在進行服務端渲染的時候,例子程式碼如下:

import { createApp } from './app'
const { app, router, store } = createApp()
router.onReady(() => {
  app.$mount('#app')
})
複製程式碼

我們會呼叫一個新方法beforeResolve,只有在router2.5.0以上的版本才會有的方法,註冊一個類似於全域性路由保護router.beforeEach(),除了在導航確認之後,在所有其他保護和非同步元件已解決之後呼叫。基本寫法如下:

router.beforeResolve((to, from, next) => {
	// to 和 from 都是 路由資訊物件
	// 返回目標位置或是當前路由匹配的元件陣列(是陣列的定義/構造類,不是例項)。通常在服務端渲染的資料預載入時時候。
	const matched = router.getMatchedComponents(to)
    const prevMatched = router.getMatchedComponents(from)
})
複製程式碼

服務端把要給客戶端的 state 放在了 window.INITIAL_STATE 這個全域性變數上面。前後端的 HTML 結構應該是一致的。然後要把 store 的狀態樹寫入一個全域性變數(INITIAL_STATE),這樣客戶端初始化 render 的時候能夠校驗伺服器生成的 HTML 結構,並且同步到初始化狀態,然後整個頁面被客戶端接管。基本程式碼如下:

// 將服務端渲染時候的狀態寫入vuex中
if (window.__INITIAL_STATE__) {
  store.replaceState(window.__INITIAL_STATE__)
}
複製程式碼

接下來貼出來完整的客戶端程式碼,這裡的Q也可以不用引入,直接使用babel就能編譯es6自帶的Promise,因為本人使用習慣了,這裡可以根據自身的需求是否安裝:

import { createApp } from './app'
import Q from 'q'
import Vue from 'vue'

Vue.mixin({
  beforeRouteUpdate (to, from, next) {
    const { asyncData } = this.$options
    if (asyncData) {
      asyncData({
        store: this.$store,
        route: to
      }).then(next).catch(next)
    } else {
      next()
    }
  }
})
const { app, router, store } = createApp()

// 將服務端渲染時候的狀態寫入vuex中
if (window.__INITIAL_STATE__) {
  store.replaceState(window.__INITIAL_STATE__)
}

router.onReady(() => {
  router.beforeResolve((to, from, next) => {
      const matched = router.getMatchedComponents(to)
      const prevMatched = router.getMatchedComponents(from)
      // 我們只關心之前沒有渲染的元件
      // 所以我們對比它們,找出兩個匹配列表的差異元件
      let diffed = false
      const activated = matched.filter((c, i) => {
        return diffed || (diffed = (prevMatched[i] !== c))
      })
      if (!activated.length) {
        return next()
      }
      // 這裡如果有載入指示器(loading indicator),就觸發
      Q.all(activated.map(c => {
        if (c.asyncData) {
          return c.asyncData({ store, route: to })
        }
      })).then(() => {
        // 停止載入指示器(loading indicator)
        next()
      }).catch(next)
    })
    app.$mount('#app')
})
複製程式碼

entry-server.js程式碼編寫:

基本編寫和客戶端的差不多,因為這是服務端渲染,涉及到與後端資料互動定義的問題,我們需要在這裡定義好各元件與後端互動使用的方法名稱,這樣方便在元件內部直接使用,這裡根我們常規在元件直接使用ajax獲取資料有些不一樣,程式碼片段如下:

//直接定義元件內部asyncData方法來觸發相應的ajax獲取資料
if (Component.asyncData) {
  return Component.asyncData({
    store,
    route: router.currentRoute
  })
}
複製程式碼

以下是完整的服務端程式碼:

import { createApp } from './app'
import Q from 'q'
export default context => {
  return new Q.Promise((resolve, reject) => {
    const { app, router, store } = createApp()
    router.push(context.url)
    router.onReady(() => {
      const matchedComponents = router.getMatchedComponents()
      if (!matchedComponents.length) {
        return reject({ code: 404 })
      }
      // 對所有匹配的路由元件呼叫 `asyncData()`
      Q.all(matchedComponents.map(Component => {
        if (Component.asyncData) {
          return Component.asyncData({
            store,
            route: router.currentRoute
          })
        }
      })).then(() => {
        // 在所有預取鉤子(preFetch hook) resolve 後,
        // 我們的 store 現在已經填充入渲染應用程式所需的狀態。
        // 當我們將狀態附加到上下文,
        // 並且 `template` 選項用於 renderer 時,
        // 狀態將自動序列化為 `window.__INITIAL_STATE__`,並注入 HTML。
        context.state = store.state
        resolve(app)
      }).catch(reject)
    }, reject)
  })
}
複製程式碼

4、腳手架其他目錄介紹:

到這裡src下面主要的幾個檔案程式碼已經編寫完成,接下里介紹下整個專案的目錄結構如下:

整個目錄結構

主要幾個檔案介紹如下:

  • build 主要存放webpack打包配置檔案
  • dist webpack打包後生成的目錄
  • log 使用pm2監控程式存放的日誌檔案目錄
  • server.js node伺服器啟動檔案
  • pmlog.json pm2配置檔案

server.js入口檔案編寫

我們還需要編寫在服務端啟動服務的程式碼server.js,我們會使用到部分node原生提供的api,片段程式碼如下:

const Vue = require('vue')
const express = require('express')
const path = require('path')
const LRU = require('lru-cache')
const { createBundleRenderer } = require('vue-server-renderer')
const fs = require('fs')
const net = require('net')
複製程式碼

大致思路是,引入前端模版頁面index.template.html,使用express啟動服務,引入webpack打包專案程式碼的dist檔案,引入快取模組(這裡不做深入介紹,後期會單獨詳細介紹),判斷埠是否被佔用,自動啟動其他介面服務。

引入前端模版檔案並且設定環境變數為production,片段程式碼如下:

const template = fs.readFileSync('./src/index.template.html', 'utf-8')
const isProd = process.env.NODE_ENV === 'production'
複製程式碼

vue-server-renderer外掛的具體使用,通過讀取dist資料夾下的目錄檔案,來建立createBundleRenderer函式,並且使用LRU來設定快取的時間,通過判斷是生產環境還是開發環境,呼叫不同的方法,程式碼片段如下:

const resolve = file => path.resolve(__dirname, file)
function createRenderer (bundle, options) {
  return createBundleRenderer(bundle, Object.assign(options, {
    template,
    cache: LRU({
      max: 1000,
      maxAge: 1000 * 60 * 15
    }),
    basedir: resolve('./dist'),
    runInNewContext: false
  }))
}
let renderer;
let readyPromise
if (isProd) {
  const bundle = require('./dist/vue-ssr-server-bundle.json')
  const clientManifest = require('./dist/vue-ssr-client-manifest.json')
  renderer = createRenderer(bundle, {
    clientManifest
  })
} else {
  readyPromise = require('./build/setup-dev-server')(server, (bundle, options) => {
    renderer = createRenderer(bundle, options)
  })
}
複製程式碼

使用express啟動服務,程式碼片段如下:

const server = express();

//定義在啟動服務錢先判斷中介軟體中的快取是否過期,是否直接呼叫dist檔案。
const serve = (path, cache) => express.static(resolve(path), {
  maxAge: cache && isProd ? 1000 * 60 * 60 * 24 * 30 : 0
})
server.use('/dist', serve('./dist', true))
server.get('*', (req, res) => {
  const context = {
    title: 'hello',
    url: req.url
  }
  renderer.renderToString(context, (err, html) => {
    if (err) {
      res.status(500).end('Internal Server Error')
      return
    }
    res.end(html)
  })
})
複製程式碼

判斷埠是否被佔用,片段程式碼如下:

function probe(port, callback) {
    let servers = net.createServer().listen(port)
    let calledOnce = false
    let timeoutRef = setTimeout(function() {
        calledOnce = true
        callback(false, port)
    }, 2000)
    timeoutRef.unref()
    let connected = false
    servers.on('listening', function() {
        clearTimeout(timeoutRef)

        if (servers)
            servers.close()

        if (!calledOnce) {
            calledOnce = true
            callback(true, port)
        }
    })
    servers.on('error', function(err) {
        clearTimeout(timeoutRef)

        let result = true
        if (err.code === 'EADDRINUSE')
            result = false

        if (!calledOnce) {
            calledOnce = true
            callback(result, port)
        }
    })
}
const checkPortPromise = new Promise((resolve) => {
    (function serverport(_port) {
        let pt = _port || 8080;
        probe(pt, function(bl, _pt) {
            // 埠被佔用 bl 返回false
            // _pt:傳入的埠號
            if (bl === true) {
                // console.log("\n  Static file server running at" + "\n\n=> http://localhost:" + _pt + '\n');
                resolve(_pt);
            } else {
                serverport(_pt + 1)
            }
        })
    })()

})
checkPortPromise.then(data => {
    uri = 'http://localhost:' + data;
    console.log('啟動服務路徑'+uri)
    server.listen(data);
});
複製程式碼

到這裡,基本的程式碼已經編寫完成,webpack打包配置檔案基本和官方保持不變,接下來可以嘗試啟動本地的專案服務,這裡簡要的使用網易嚴選首頁作為demo示例,結果如下:

demo

第三部分 mockjs和axios配合使用

1、前言

上一節大致介紹了服務端和客戶端入口檔案程式碼內容,現在已經可以正常執行你的後端渲染腳手架了,這一節,跟大家分享下如何使用axios做ajax請求,如何使用mockjs做本地假資料,跑通本地基本邏輯,為以後前後端連調做準備。

2、前期準備

需要用npm安裝axios,mockjs依賴包,由於mockjs只是程式碼開發的輔助工具,所以安裝的時候我會加--save-dev來區分,具體可以根據自己的需求來定,當然,如果有mock服務平臺的話,可以直接走mock平臺造假資料,本地直接訪問mock平臺的介面,例如可以使用阿里的Rap平臺管理工具生成。

npm install axios --save
npm install mockjs --save-dev
複製程式碼

3、簡要介紹axios

其他請求方式,程式碼示例如下:

axios.request(config);
axios.get(url[,config]);
axios.delete(url[,config]);
axios.head(url[,config]);
axios.post(url[,data[,config]]);
axios.put(url[,data[,config]])
axios.patch(url[,data[,config]])
複製程式碼

具體詳細可以點選檢視axios基本使用介紹

api.js完整程式碼如下:

import axios from 'axios'
import qs from 'qs'
import Q from 'q'
/**
 * 相容 不支援promise 的低版本瀏覽器
 */
require('es6-promise').polyfill();
import C from '../conf'

axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'
axios.defaults.withCredentials = true

function ajax(url, type, options) {

  return Q.Promise((resolve, reject) => {
    axios({
        method: type,
        url: C.HOST + url,
        params: type === 'get' ? options : null,
        data: type !== 'get' ? qs.stringify(options) : null
      })
      .then((result) => {
        if (result && result.status === 401) {
          // location.href = '/views/401.html'
        }
        if (result && result.status === 200) {
          if (result.data.code === 200) {
            resolve(result.data.data);
          } else if (result.data.code === 401) {
            reject({
              nopms: true,
              msg: result.data.msg
            });
          } else {
            reject({
              error: true,
              msg: result.data.msg
            });
          }
        } else {
          reject({
            errno: result.errno,
            msg: result.msg
          });
        }
      })
      .catch(function(error) {
        console.log(error, url);
      });
  })
}

const config = {
  get(url, options) {
    const _self = this;
    return Q.Promise((resolve, reject) => {
      ajax(url, 'get', options)
        .then((data) => {
          resolve(data);
        }, (error) => {
          reject(error);
        });
    })
  },

  post(url, options) {
    const _self = this;
    return Q.Promise((resolve, reject) => {
      ajax(url, 'post', options)
        .then((data) => {
          resolve(data);
        }, (error) => {
          reject(error);
        });
    })
  },

  put(url, options) {
    const _self = this;
    return Q.Promise((resolve, reject) => {
      ajax(url, 'put', options)
        .then((data) => {
          resolve(data);
        }, (error) => {
          reject(error);
        });
    })
  },

  delete(url, options) {
    const _self = this;
    return Q.Promise((resolve, reject) => {
      ajax(url, 'delete', options)
        .then((data) => {
          resolve(data);
        }, (error) => {
          reject(error);
        });
    })
  },

  jsonp(url, options) {
    const _self = this;
    return Q.Promise((resolve, reject) => {
      ajax(url, 'jsonp', options)
        .then((data) => {
          resolve(data);
        }, (error) => {
          reject(error);
        });
    })
  }
};

export default config;
複製程式碼

mockjs專案基本配置如下:

1、在public下新建conf.js全域性定義請求url地址,程式碼如下:

module.exports = {
    HOST: "http://www.xxx.com",
    DEBUGMOCK: true
};
複製程式碼

2、在views/index根目錄下新建conf.js,定義元件mock的請求路徑,並且定義是否開始單個元件使用mock資料還是線上介面資料,程式碼如下:

const PAGEMOCK = true;
const MODULECONF = {
  index: {
    NAME: '首頁',
    MOCK: true,
    API: {
      GET: '/api/home',
    }
  }
};
複製程式碼

3、在元件內部定義mockjs來編寫mock假資料,程式碼如下:

import Mock from 'mockjs';
const mData = {
  index: {
    API: {
      GET: {
        "code": 200,
        "data": {
          "pin": 'wangqi',
          "name": '王奇'
        }
      }
    }
  }
}
複製程式碼

以上就是基本的流程,如果有更好更靈活的使用方案,希望能夠參與溝通並且分享,專案工作流已經在github上分享,並且會繼續維護更新,點選檢視詳情

相關文章