安装
npm install mockjs
mockjs原理
拦截所有请求并代理到本地,然后进行数据模拟。
使用mockjs出现的问题
一些底层依赖XMLHttpRequest的库都会和它发生不兼容,本地调试只能通过看源码或者Debug出来
解决方案
如果是本地运行则会启动mock-serve运行,线上环境还是继续使用mockjs来进行模拟。一套mock数据可以多环境使用
工程目录准备
├── mock // mock 模拟数据 与src同级
│ ├── index.js // 数据总入口
│ ├── mock-server.js // 数据服务层
│ ├── article.js // 数据块
开始
1、将本地的请求会代理到http://localhost:${port}/mock下。mock-server Node中间件拦截实现,注入方式
proxy: {
[process.env.VUE_APP_BASE_API]: {
target: `http://192.168.123.149:${port}/mock`,
changeOrigin: true,
pathRewrite: {
['^' + process.env.VUE_APP_BASE_API]: ''
}
}
},
after: require('./mock/mock-server.js')
2、数据块article.js,实现对单组接口统一的定义
import Mock from 'mockjs'
const List = []
const count = 100
const baseContent = '<p>I am testing data, I am testing data.</p><p><img src="https://wpimg.wallstcn.com/4c69009c-0fd4-4153-b112-6cb53d1cf943"></p>'
const image_uri = 'https://wpimg.wallstcn.com/e4558086-631c-425c-9430-56ffb46e70b3'
for (let i = 0; i < count; i++) {
List.push(Mock.mock({
id: '@increment',
timestamp: +Mock.Random.date('T'),
author: '@first',
reviewer: '@first',
title: '@title(5, 10)',
content_short: 'mock data',
content: baseContent,
forecast: '@float(0, 100, 2, 2)',
importance: '@integer(1, 3)',
'type|1': ['CN', 'US', 'JP', 'EU'],
'status|1': ['published', 'draft', 'deleted'],
display_time: '@datetime',
comment_disabled: true,
pageviews: '@integer(300, 5000)',
image_uri,
platforms: ['a-platform']
}))
}
export default [
{
url: '/article/list',
type: 'get',
response: config => {
const { importance, type, title, page = 1, limit = 20, sort } = config.query
let mockList = List.filter(item => {
if (importance && item.importance !== +importance) return false
if (type && item.type !== type) return false
if (title && item.title.indexOf(title) < 0) return false
return true
})
if (sort === '-id') {
mockList = mockList.reverse()
}
const pageList = mockList.filter((item, index) => index < limit * page && index >= limit * (page - 1))
return {
code: 20000,
data: {
total: mockList.length,
items: pageList
}
}
}
},
{
url: '/article/detail',
type: 'get',
response: config => {
const { id } = config.query
for (const article of List) {
if (article.id === +id) {
return {
code: 20000,
data: article
}
}
}
}
},
{
url: '/article/pv',
type: 'get',
response: _ => {
return {
code: 20000,
data: {
pvData: [
{ key: 'PC', pv: 1024 },
{ key: 'mobile', pv: 1024 },
{ key: 'ios', pv: 1024 },
{ key: 'android', pv: 1024 }
]
}
}
}
},
{
url: '/article/create',
type: 'post',
response: _ => {
return {
code: 20000,
data: 'success'
}
}
},
{
url: '/article/update',
type: 'post',
response: _ => {
return {
code: 20000,
data: 'success'
}
}
}
]
3、index.js 模拟数据的导入导出,路由批量注册,以及公用逻辑实现,同时兼容了Mock和中间件的使用
import Mock from 'mockjs'
import { param2Obj } from '../src/utils'
import article from './article'
const mocks = [
...article
]
export function mockXHR() {
Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
Mock.XHR.prototype.send = function() {
if (this.custom.xhr) {
this.custom.xhr.withCredentials = this.withCredentials || false
if (this.responseType) {
this.custom.xhr.responseType = this.responseType
}
}
this.proxy_send(...arguments)
}
function XHR2ExpressReqWrap(respond) {
return function(options) {
let result = null
if (respond instanceof Function) {
const { body, type, url } = options
result = respond({
method: type,
body: JSON.parse(body),
query: param2Obj(url)
})
} else {
result = respond
}
return Mock.mock(result)
}
}
for (const i of mocks) {
Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response))
}
}
const responseFake = (url, type, respond) => {
return {
url: new RegExp(`/mock${url}`),
type: type || 'get',
response(req, res) {
res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond))
}
}
}
export default mocks.map(route => {
return responseFake(route.url, route.type, route.response)
})
4、mock-serve中间件逻辑实现分两部分进行讲述,第一节是路由的批量注册,第二节是文件监听实现自动重启服务
// 监听文件或文件夹的变化
const chokidar = require('chokidar')
// 处理程序之前,在中间件中对传入的请求体进行解析
const bodyParser = require('body-parser')
// 输出不再单调,添加文字背景
const chalk = require('chalk')
// 处理文件路径
const path = require('path')
const mockDir = path.join(process.cwd(), 'mock')
function registerRoutes(app) {
let mockLastIndex
const { default: mocks } = require('./index.js')
for (const mock of mocks) {
app[mock.type](mock.url, mock.response)
mockLastIndex = app._router.stack.length
}
const mockRoutesLength = Object.keys(mocks).length
return {
mockRoutesLength: mockRoutesLength,
mockStartIndex: mockLastIndex - mockRoutesLength
}
}
function unregisterRoutes() {
Object.keys(require.cache).forEach(i => {
if (i.includes(mockDir)) {
delete require.cache[require.resolve(i)]
}
})
}
// 请求→响应头解析→注册监听逻辑→清除缓存→响应服务
module.exports = app => {
require('@babel/register')
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({
extended: true
}))
const mockRoutes = registerRoutes(app)
var mockRoutesLength = mockRoutes.mockRoutesLength
var mockStartIndex = mockRoutes.mockStartIndex
// watch files, hot reload mock server
chokidar.watch(mockDir, {
ignored: /mock-server/,
ignoreInitial: true
}).on('all', (event, path) => {
if (event === 'change' || event === 'add') {
try {
// remove mock routes stack
app._router.stack.splice(mockStartIndex, mockRoutesLength)
// clear routes cache
unregisterRoutes()
const mockRoutes = registerRoutes(app)
mockRoutesLength = mockRoutes.mockRoutesLength
mockStartIndex = mockRoutes.mockStartIndex
console.log(chalk.magentaBright(`\n > Mock Server hot reload success! changed ${path}`))
} catch (error) {
console.log(chalk.redBright(error))
}
}
})
}
5、main.js 当前运行环境判定挂载mock对象(mock-server只会在线下开发环境中使用)
import { mockXHR } from '../mock'
if (process.env.NODE_ENV === 'development') {
mockXHR()
}