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))
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章