vue-element-admin源码解读——数据Mock


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目录下的userrolearticleremote-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))
}

版权声明:本文为qq_41115702原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。