MOCK
是指模拟服务器按照一定的规则或者设定的数据,对客户端的请求进行响应。换个说法就是可以不用搭建后台服务器,就可以实现前端对数据的请求的响应。在vue-element-admin
框架中采用的是MockJS
。
MockJS
的原理是拦截了所有的请求并代理到本地,然后进行数据模拟,所以你会发现 network 中没有发出任何的请求。不过在vue-element-admin
框架中是利用webpack-dev-serve
来实现的,在你启动前端服务的同时,mock-server
就会自动启动,而且这里还通过 chokidar
来观察 mock
文件夹内容的变化.在Vue.config.js
中可以看到有如下配置:
devServer: {
port: port,
open: true,
overlay: {
warnings: false,
errors: true
},
//通过这一行来实现在webpack-dev-serve启动之前启动mock-server服务的
before: require('./mock/mock-server.js')
},
那么就来看看mock-server.js
中是如何实现的:
const chokidar = require('chokidar')
const bodyParser = require('body-parser')
const chalk = require('chalk')
const path = require('path')
const Mock = require('mockjs')
//获取mock静态返回数据所在的目录
const mockDir = path.join(process.cwd(), 'mock')
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
//监测mock目录下的文件变化
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)
// 清楚路由缓存数据
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))
}
}
})
}
我们重点来看看这个响应理由是如何被注册的:
function registerRoutes(app) {
let mockLastIndex
//这里就是返回的是MockXHR,即利用
const { default: mocks } = require('./index.js')
const mocksForServer = mocks.map(route => {
//重点是这里
return responseFake(route.url, route.type, route.response)
})
for (const mock of mocksForServer) {
app[mock.type](mock.url, mock.response)
mockLastIndex = app._router.stack.length
}
const mockRoutesLength = Object.keys(mocksForServer).length
return {
mockRoutesLength: mockRoutesLength,
mockStartIndex: mockLastIndex - mockRoutesLength
}
}
const responseFake = (url, type, respond) => {
return {
//这里就将url上下文前缀拼接在一起
url: new RegExp(`${process.env.VUE_APP_BASE_API}${url}`),
//默认get方法
type: type || 'get',
response(req, res) {
console.log('request invoke:' + req.path)
//写回json数据
// Mock.mock(templ)即返回templ数据
res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond))
}
}
}
虽然代码表面使用的是MockJs
,但是实际上用的却是封装过的MockXHR
,这个封装过程如下:
//
import Mock from 'mockjs'
import { param2Obj } from '../src/utils'
import user from './user'
import role from './role'
import article from './article'
import search from './remote-search'
//模拟数据
const mocks = [
...user,
...role,
...article,
...search
]
export function mockXHR() {
//这里重写了Mock的代理方法,重新定义XMLHttpRequest
//这里将XHR指向了webpack-dev-serve的XHR
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))
}
}
export default mocks
通过重新包装MockJS
之后,调用的是原生的send
函数,所以在network
中也可以看得见数据的传输。
Mock数据编写在了Mock
目录下的user
、role
、article
、remote-search
文件中,通过上述文件统一引入mocks的数组中。
const mocks = [
...user,
...role,
...article,
...search
]
然后通过遍历数组绑定在了Mock的内部映射表中。
for (const i of mocks) {
Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response))
}