简单聊聊前端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: