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))
}