簡單聊聊前端mock

爲何須要mock

​ 如今不少的 web 項目都是先後端徹底分離的項目,後端負責提供數據,前端經過請求api 接口獲取數據。這個帶來的一個現象就是前端頁面寫好,後端接口功能沒有開發完,形成數據阻塞,影響開發效率。若是這個時候先後端已經定義好接口規範,前端徹底能夠本身定義一份數據去模擬,完成功能開發。前端

Mock 的使用場景

  • web:在大部分公司前端開發仍是主要以web頁面爲主,基本上涉及到就是與服務端的請求交互,這個時候咱們只須要單純的考慮與服務端的http請求的處理。vue

  • app: 現下不少公司的app項目已經不在使用原生的去開發,hybrid開發模式在興起。這個時候咱們不只要考慮與服務端的交互,還要考慮與native的數據交互。一般app出去演示的時候咱們還要考慮到斷網的狀況下怎麼去完整的跑完整個流程。webpack

mock的方式

​ 首先咱們要明確一點,不論是數據mock仍是真實環境,這兩個都是獨立的,不該該在業務代碼中去改動。理論上好的方式是經過一個配置或者一個變量去控制mock的開啓,業務代碼中只需正常的寫一次方案便可。git

​ 這樣的話咱們就能夠pass掉外部引入到內部去替換數據這種作法,或者說在內部寫一份臨時數據,這都是不可取的,會致使業務耦合嚴重,不利於維護和開發。github

​ 一般咱們能夠在項目內部定義一個文件夾專門去存放mock相關的邏輯,當項目比較龐大時還要考慮一下模塊的拆分,等等。。。web

​ 在我實際開發中碰到的場景來講,我我的理解上會把mock分爲三種形式數據庫

  • 請求攔截
  • 中間件middleware
  • 緩存

請求攔截

這種比較典型的就是mockjs,mockjs會在你請求發出以前,攔截請求,按照你的響應數據模版直接返回數據。這種方式通常是咱們用的最多的方式,它並不須要額外的去啓動服務器。一個簡單的例子:npm

import Mock from 'mockjs'
Mock.mock('/user/getList', () => {
  return Mock.mock({
    errno: 0,
    data: {
      'list|10': [
        {
          'id|+1': 2,
          'name': '@cname()',
          'time': '@datetime("yyyy-MM-dd HH:mm:ss")',
          'address': '@county(true)'
        }
      ]
    }
  })
})
複製代碼

實際出現的結果就是介個樣子json

mockjs.png

詳細的能夠看官網Mock.jS後端

中間件middleware

這類的實現方式通常就是webpack啓動server, 在devServer的before鉤子中實現。配置以下:

{
  ...
  devServer: {
    before: (app) => {
      app.get('/user/getTaskList', function (req, res) => //返回處理的數據) } } } 複製代碼

一樣咱們能夠定義一份配置文件,經過編寫一箇中間件去讀取配置。

而後結合mockjs去生成隨機數據,例如:

// mock-api.js
let Mock = require('mockjs')
module.exports = {
  'GET /user/getTaskList': Mock.mock({
    errno: 0,
    data: {
      'list|10': [
        {
          taskName: '@word(4)',
          'taskId|+1': 2,
          desc: '@csentence(5, 20)'
        }
      ] 
    }
  }),
}
複製代碼

咱們也能夠直接使用前人已經寫好的中間件,好比webpack-api-mocker,經過npm install -D webpack-api-mocker --save-dev, 而後在咱們的webpack的配置文件中,用這個去替換咱們以前寫的

// webpack.config.js
const webpackApiMock = require('webpack-api-mocker');
module.exports = {
  ...
  devServer: {
    before: (app) => {
      apiMocker(app, path.resolve('mock-api.js'))
    }
  }
}
複製代碼

到這裏基本上已經搭建好一個簡單的mock服務了,接下來咱們還須要加個一個變量和命令去啓動mock。

在package.json文件中添加快捷命令

{
  ...
  "scripts": {
    ...
    "mock": "NODE_ENV=mock webpack-dev-server --color --progress"
  }
  ...
}
複製代碼

在webpack配置文件裏添加判斷

// webpack.config.js
const webpackApiMock = require('webpack-api-mocker');
const isMock = process.env.NODE_ENV === 'mock'
let devServer = {
  ....
}
// 添加判斷邏輯
if (isMock) {
  devServer.before = (app) => {
    apiMocker(app, path.resolve('mock-api.js'))
  }
}
module.exports = {
  ...
  devServer
}
複製代碼

緩存

​ 這種通常是在H5開發中會比較常見,混合開發模式下,咱們不只要對接server端,還要和native端交互,這個時候咱們還要考慮一下在本地與服務端調試的過程當中怎麼去mock原生native端的數據確保流程能正常走下去。或許還會有這麼一個場景,在後端未開發完成的狀況下,咱們須要展現給客戶看一個能正常走通流程的應用。這種理想的狀況下就是咱們能模擬後端去實現數據的存儲,增、刪、改、查。

前面也提到,這種須要本地去模擬數據庫,現代瀏覽器也提供了不少數據存儲方案:

  • cookie:存儲大小不超過4kb,並且每次請求瀏覽器都會在請求頭帶上
  • localStorage/sessionStorage: 存儲大小通常不到10M,並且sessionStorage退出窗口會清楚緩存
  • indexedDB: IndexedDB 容許儲存大量數據,提供查找接口,還能創建索引

這裏咱們不關心究竟使用哪一種緩存方式,固然我的更推薦indexedDB, 他的一些列鍵值對儲存異步處理支持事務、**存儲量大(這個很重要)**等特色可使的咱們更方便去實現接口的功能。

固然這裏咱們不過多的去深究,有興趣的朋友能夠本身去看IndexedDB,這裏咱們主要討論一下實現。

前面咱們有提到過,數據mock最重要的一點就是不能入侵代碼,不然後續的調試會讓人至關頭疼。個人思路是按照請求攔截的思路,去編寫一箇中間類,全部調用服務端或者native的都先通過這個類, 經過變量去控制實際轉發的究竟是mock地址仍是真實請求。

簡單的一個僞代碼例子以下:

// service.js

import http from '@/service/http';
import httpMock from '@/service/mock'
import native from '@/native/native';
import nativeMock from '@/native/mock'
// ... 省略中間代碼
class ServiceWork {
  public mock: boolean // 開啓所有mock
  public nativeMock: boolean // 單獨開啓native mock
  public httpMock: boolean // 單獨開啓服務端mock
  // ... 省略中間代碼
  public callHttp (method: string, data?: any) {
    let servers: API = this.mock || this.httpMock ? httpMock : http
    let err = this.handleError(servers[method], method, 'httpApi')
    if (err) {
      return Promise.reject(new Error(err))
    }
    return servers[method](data)
  }
  public callNative (method: string, data?: any) {
    let natives: Native = this.mock || this.nativeMock ? nativeMock : native
    let err = this.handleError(natives[method], method, 'nativeApi')
    if (err) {
      return Promise.reject(new Error(err))
    }
    return natives[method](data)
  }
  private handleError (fun: string|Function, method: string, type: string) {
    if (!fun || typeof fun !== 'function') {
      return `${method} is not defined at ${type} or ${method} is not a function`
    }
    return false
  }
  // ...省略其餘代碼
}

export default ServiceWork;

複製代碼

Mock 或者 http代碼:

// task mock
import IDB from '@/lib/db'; // 基於indexDB封裝的一個簡單類
const db = new IDB('collect');
const collect = storage.collection('task');
export default {
  addTask: (params) => {
    return collect.add(params)
  }
  // ...
}
複製代碼

ps: 以上操做是基於indexedDB封裝的簡單類,若是想使用功能更強大的能夠參考zangodb

項目中使用的是vue,因此經過vue的插件機制將service實例掛載到Vue.prototype上

// service
import ServiceWork from './service.js'
function install (Vue: VueConstructor) {
  Vue.prototype.$service = new ServiceWork()
}

export default {
  version; '1.0.0',
  install
}
複製代碼

接下來就只要在vue代碼中經過this.$service.callHttp 或者 this.$service.callNative 調用native 和 http方法。同時能夠動態的設置this.$service.mock = true 控制是否開啓mock功能。

至此,基本的設計就已經完成。

總結

本文只是簡單的總結了一下我的實際開發中遇到的mock場景,文中不少東西講的可能並非特別詳細,這些方法各自都有一些不足的地方。感興趣的朋友們能夠本身深刻的去實際感覺對比一下優劣性, 在本身實際開發中選擇一種適合本身的。另,本文中若有講的不對或者不足的地方,望及時回覆指出,不勝感激:smile:

相關文章
相關標籤/搜索