vue中使用mockjs详解及原理分析

338 阅读3分钟

开发的过程中在后端接口还没准备好的情况下,前端可以自己mock调试。mock的方式很多,简单的可以自己直接写json赋值,mockjs这种方式是通过拦截原生window.XMLHttpRequest,对业务代码的侵入性更小一点。然而查看mockjs的官网,发现应用文档太少,没有基于框架的引用,故查阅了一番做此整理,并简单解析了一下源码。

整体步骤

  1. npm安装mockjs
  2. 新建mock文件,导入mockjs
  3. main.ts中导入mock文件

具体步骤

  • npm install mockjs
  • src同级创建mock文件夹
  • 新建index.ts,读取其他的mock业务文件
  • 新建业务mock文件
  • main.ts中通过import()按需导入mock./index.ts,可以控制是否mock拦截

知识点: 标准用法的 import 导入的模块是静态的,会使所有被导入的模块,在加载时就被编译(无法做到按需编译,降低首页加载速度)。有些场景中,你可能希望根据条件导入模块或者按需导入模块,这时你可以使用动态导入代替静态导入。

使用姿势:

// mock/index.ts
// 首先引入Mock
import Mock from 'mockjs'
import { CONF, URL_PRE } from '@/config'

// 设置拦截ajax请求的相应时间
Mock.setup({
  timeout: '200-600',
})

interface IService {
  path: string
  type: string
  data: any
}

let configArray: IService[] = []

// 使用webpack的require.context()遍历所有mock文件
const files = require.context('.', true, /\.ts$/)
files.keys().forEach((key) => {
  if (key === './index.ts') return
  configArray = configArray.concat(files(key).default)
})

// 注册所有的mock服务
configArray.forEach((item) => {
  for (const [key, value] of Object.entries(item)) {
    const { path, type, data } = value
    Mock.mock(`${CONF.apiEndpoint}${URL_PRE}${path}`, type, data)
  }
})
// getHost.ts
const hosteList = {
     // mock数据
}

const hosteList2 = {
    // mock数据
}


const getHostList = {
  path: '/mockpath',
  type: 'get',
  data: {
    hostList,
  },
}

const getHostList2 = {
  path: '/mockpath2',
  type: 'get',
  data: {
    hostList2,
  },
}

export default { getHostList, getHostList2 }
// main.ts
// 设置开关,通过动态引入的方式引入mock
const IS_MOCK = true
if(IS_MOCK) {
    import('./mock')
}

mockjs原理

  1. 自定义Mock对象,XHR属性用来拦截的原生window.XMLHttpRequest,_mocked属性用来存储拦截的请求
  2. 由于拦截了原生XHR,所以在http请求的时候,会走Mock.XHR逻辑
  3. Mock.XHR,即MockXMLHttpRequest,同样封装了跟原生XHR相同的open(), send(),abort()相同的方法
  4. open()中,在Mock._mocked中查找是否有匹配的请求,有的话开始拦截,标记拦截请求match;没有的话采用原生 XHR 发送请求。
  5. send()中,根据拦截标志位match,判断是否采用原生XHR.send();abort()中也同理。
// mock.js
var XHR
if (typeof window !== 'undefined') XHR = require('./mock/xhr')

var Mock = {
    Handler: Handler,
    Random: Random,
    Util: Util,
    XHR: XHR,
    RE: RE,
    toJSONSchema: toJSONSchema,
    valid: valid,
    heredoc: Util.heredoc,
    setup: function(settings) {
        return XHR.setup(settings)
    },
    _mocked: {}
}

// 避免循环依赖
if (XHR) XHR.Mock = Mock

Mock.mock = function(rurl, rtype, template) { 
    // 拦截 XHR:将自定义的XHR赋值给原生的window.XMLHttpRequest
    if (XHR) window.XMLHttpRequest = XHR
    // 将每一次的mock请求存到_mocked对象中
    Mock._mocked[rurl + (rtype || '')] = {
        rurl: rurl,
        rtype: rtype,
        template: template
    }
    return Mock
}

module.exports = Mock
// xhr.js
function MockXMLHttpRequest() {
    // 初始化 custom 对象,用于存储自定义属性
    this.custom = {
        events: {},
        requestHeaders: {},
        responseHeaders: {}
    }
}

// 标记当前对象为 MockXMLHttpRequest
MockXMLHttpRequest.prototype.mock = true

// 是否拦截 Ajax 请求
MockXMLHttpRequest.prototype.match = false

// 初始化 Request 相关的属性和方法
Util.extend(MockXMLHttpRequest.prototype, {
    // https://xhr.spec.whatwg.org/#the-open()-method
    // Sets the request method, request URL, and synchronous flag.
    open: function(method, url, async, username, password) {  
        // 查找与请求参数匹配的数据模板
        var item = find(this.custom.options)
        // 如果未找到匹配的数据模板,则采用原生 XHR 发送请求。
        if (!item) {
            // 创建原生 XHR 对象,调用原生 open(),监听所有原生事件
            var xhr = createNativeXMLHttpRequest()
            this.custom.xhr = xhr
        }
        // 找到了匹配的数据模板,开始拦截 XHR 请求
        // 标记拦截请求
        this.match = true
        this.readyState = MockXMLHttpRequest.OPENED
    }
    send: function send(data) {
        var that = this
        this.custom.options.body = data

        // 原生 XHR
        if (!this.match) {
            this.custom.xhr.send(data)
            return
        }

        // 拦截 XHR
        if (this.custom.async) setTimeout(done, this.custom.timeout) // 异步延迟执行done
        else done() // 同步直接执行done
        
        function done() {
            that.readyState = MockXMLHttpRequest.HEADERS_RECEIVED
            that.dispatchEvent(new Event('readystatechange' /*, false, false, that*/ ))
            that.readyState = MockXMLHttpRequest.LOADING
            that.dispatchEvent(new Event('readystatechange' /*, false, false, that*/ ))

            that.status = 200
            that.statusText = HTTP_STATUS_CODES[200]

            // fix #92 #93 by @qddegtya
            that.response = that.responseText = JSON.stringify(
                convert(that.custom.template, that.custom.options),
                null, 4
            )

            that.readyState = MockXMLHttpRequest.DONE
            that.dispatchEvent(new Event('readystatechange' /*, false, false, that*/ ))
            that.dispatchEvent(new Event('load' /*, false, false, that*/ ));
            that.dispatchEvent(new Event('loadend' /*, false, false, that*/ ));
        }
    }
    abort: function abort() {
        // 原生 XHR
        if (!this.match) {
            this.custom.xhr.abort()
            return
        }

        // 拦截 XHR
        this.readyState = MockXMLHttpRequest.UNSENT
        this.dispatchEvent(new Event('abort', false, false, this))
        this.dispatchEvent(new Event('error', false, false, this))
    }