Mock.mock( rurl, rtype, template )拦截ajax请求——帮助前端离线开发

本文详细介绍MockJS的使用方法,包括快速开始、动态生成mock请求、关键API介绍及源码探究,帮助开发者在前后端分离的开发模式下,利用MockJS进行高效的数据模拟。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、问题描述

在工作中,想自己根据后台模拟的数据,保存到本地,在网络不通的情况下,同样的写ajax请求逻辑代码,不需要改代码模拟ajax请求,获取本地的模拟的数据,能够正常测试和开发,如果有开发环境,可以一键切换到开发环境中,而不需要切换代码

二、文章参考

  1. Mock.mock(rurl, rtype, template) 函数
  2. mockjs Doc
  3. Mock.mock(url,rtype, template)劫持ajax的原理(个人理解)
  4. Mock.mock() 方法介绍
  5. Mockjs如何拦截带参数的GET请求

三、快速开始

3.1 安装Mockjs

npm install mockjs -D

3.2 使用案例

import Mock from "mockjs";

//拦截请求,返回假数据
Mock.mock("http://10.91.7.159:9080/taxloan/productStat?customerId=sdf", {
  message: "调用成功",
  data: [
    { name: "XXXX1", code: "P00003" },
    { name: "XXXX2", code: "P00028" },
    { name: "XXXX3", code: "P00002" },
    { name: "XXXX4", code: "P00003" }
  ],
  rtn: "0"
});
  1. 定义的URL 请求,直接放到浏览器中运行,实际上是没有响应的,估计是在发送ajax请求的时候,mockjs做了一个拦截处理,将响应的数据变为模拟的数据了
  2. 例子中,可以使用GET 或者 POST 请求

3.3 前端发送ajax请求

export default {
	methods: {
		// 获取地理位置信息
		getLocationData: function () {
		  const that = this
		  http.get('http://10.91.7.159:9080/taxloan/productStat?customerId=sdf').then(function (resp) {
			console.log(resp)
			debugger
		  })
		},
	}
}

说明:

  1. 请求实际上没有发送到后台,而是被本地拦截了,因此 chrome开发工具查询不到请求的内容
  2. 代码逻辑实际上是是走的ajax请求,保证代码不用做任何修改

四、根据配置文件,异步加载mockjs 配置文件

main.js 启动文件中根据配置是否引用mock数据

import Vue from 'vue'
import App from './App.vue'

// 根据Vue的环境变量模式中获取配置信息,异步加载配置文件
// process.env.VUE_APP_MOCK 获取到的不是boolean,而是字符串
if (process.env.NODE_ENV === 'development' && process.env.VUE_APP_MOCK !== 'false') {
  import('./data/mockeData').then(_ => {
    console.log('引入mock数据成功')
  })
}

new Vue({
  render: h => h(App),
}).$mount('#app')

定义.env.development 的配置信息

VUE_APP_MOCK = false

Vue如何区分开发模式?

请参考 vue @vue/cli3环境变量和模式

五、动态生成mock 请求拦截

如果需要灵活配置哪些接口是需要使用mock数据的,哪些是需要继续从后台获取的,那么就需要后台返回对应的接口和数据,

vue 动态创建mock数据

  1. 准备测试数据 test.json
{
    "/hnhsav/ui/v1/myAlarm/getAlarmColor": {
        "code": "0",
        "msg": "success",
        "data": {
            "red": 1000,
            "yellow": 8,
            "green": 6,
            "blue": 4
        }
    },
    "/hnhsav/ui/v1/myAlarm/regionTop10": {
        "code": "0",
        "msg": "success",
        "data": [
            {
                "name": "测试区域",
                "count": 34419,
                "sortNum": 1
            }
        ]
    },
}
  1. 使用vue关联返回的数据
<template>
  <div class="index">
    <Panel v-if="isShowContent"/>
  </div>
</template>
<script>
import Panel from './panel'
import axios from 'axios'
import Mock from 'mockjs'
export default {
  components: {
    Panel
  },
  data () {
    return {
      isShowContent: false
    }
  },
  mounted () {
    this.getMockConfig()
  },
  methods: {
    async getMockConfig () {
      const { data } = await axios.get('/hnhsav/static/data/urlData.json')
      // console.log(data)
      // console.log(typeof data)
      // console.log(JSON.parse(data))
      // // debugger
      Object.keys(data).forEach(key => {
        Mock.mock(key, data[key])
      })
      this.isShowContent = true
    }
  }
}
</script>

六、 Mock 关键API介绍说明

6.1 Mock.mock( rurl, rtype, template ) 函数介绍

  1. Mock.mock( rurl, function( options ) )

记录用于生成响应数据的函数。当拦截到匹配 rurl 的 Ajax 请求时,函数 function(options) 将被执行,并把执行结果作为响应数据返回。

  1. Mock.mock( rurl, rtype, function( options ) )

记录用于生成响应数据的函数。当拦截到匹配 rurl 和 rtype 的 Ajax 请求时,函数 function(options) 将被执行,并把执行结果作为响应数据返回。

  1. rurl 可选。
    表示需要拦截的 URL,可以是 URL 字符串或 URL 正则。例如 //domain/list.json/、‘/domian/list.json’。

  2. rtype 可选。
    表示需要拦截的 Ajax 请求类型。例如 GET、POST、PUT、DELETE 等。

  3. template 可选。
    表示数据模板,可以是对象或字符串。例如 { ‘data|1-10’:[{}] }、‘@EMAIL’。

6.2 怎么区分不同的参数呢?

在工作中想使用POST请求方式获取数据字典的数据,由于不同的数据字典需要传递不同的参数,但是接口是同一个,这样就尴尬了,因为URL是固定的,那么怎么区分不同的参数呢?

6.3 POST 请求,相同的URL不同的参数

import Mock from 'mockjs'
// 证件类型
Mock.mock(`/${ctx}/api/v1/dict/getDictItem`, function (options) {
  console.log(options)
  if (options.body === '["aaa"]') {
    return {
      code: '0',
      msg: 'success',
      data: {
        msg: '我是A请求'
      }
    }
  } else if (options.body === '["bbb"]') {
    return {
      code: '0',
      msg: 'success',
      data: {
		msg: '我是B请求'
      }
    }
  }
})

6.4 GET 请求,相同的URL不同的参数

参照 上面POST的请求,URL地址相同,但是参数不同的解决办法

Mock.mock(RegExp(url + ".*"), "get", mockData);

import Mock from 'mockjs'
Mock.mock(RegExp('/hsdavs/hydrology/v1/getStationData\\?stcd\\=' + '.*'), {
  'code': '0',
  'msg': 'success',
  'data': {
    'wlTime': '2024-04-17T11:20:00.000+08:00',
    'wlData': '34.25',
    'flowTime': '2024-04-15T08:00:00.000+08:00',
    'flowData': '161.0',
    'jstime': '2024-04-17T00:00:00.000+08:00',
    'jsdata': 'null'
  }
})

Mock.mock(RegExp('/hsdavs/hydrology/v1/platform' + '.*'), {
  'code': '0',
  'msg': 'success',
  'data': {
    'platformIp': '10.43.216.17',
    'platformProtocol': 'https',
    'platformPort': '10443',
    'multiRouteId': 0,
    'platformLanguage': 'zh_CN',
    'token': 'SElLIFhPTWdtL1RIa2g2cXNRRnc6R0xNQ1VBeUlpVnhJOTVLQU1mdmlZbnhTWTR4Nk5iU3djdFNrM2grNXdJbz06MTcxMzMyNDcxMjkxNQ=='
  }
})

Mock.mock(RegExp('/label/v1/startTestFlow' + '.*'), function (options) {
  return {
    'code': '0',
    'msg': 'SUCCESS',
    'data': null
  }
})

注意: 如果正则表达式中有 ? 则需要转义字符,例如

在这里插入图片描述

七、Mock.mock(url, template)的原理

查看了mockjs的源码,实际上是内部是采用ajax劫持 —— 将本地的

  1. if (XHR) window.XMLHttpRequest = XHR // 拦截 XHR
  2. 根据 url+type 作为关键字,缓存 template,即根据 url+type查询数据
  3. 如果查询条件匹配得到,则使用缓存的数据,如果查询不到,则使用原生的ajax根据URL地址请求服务器

错误案例解析

api相对路径请求,mockjs查询不到数据

  1. 定义模拟的数据
import Mock from 'mockjs'
Mock.mock('https://11.11.11.11/api/aaa/bbb', { 'code': '0', 'msg': 'success', 'data': 'dlsajfkdas' })
  1. 调用的API
import axios from 'axios'
export const hungupService = () => {
  return axios({
    method: 'post',
    url: 'api/aaa/bbb'
  })
}
  1. 结论
    浏览器还是向服务器发送了请求

  2. 错误理解
    调用 ‘api/aaa/bbb’ 这个uri,浏览器默认会添加上下文转换为’https://11.11.11.11/api/aaa/bbb’这个请求,因此mock模拟数据的时候直接填写全路径

  3. 问题解析
    因为mockjs判断是否拦截ajax请求,是根据 uri+type这两个因素来决定的,因为下面调用的是’api/aaa/bbb’,因此mockjs会根据这个’api/aaa/bbb’去查找,而不是根据浏览器转换之后的’https://11.11.11.11/api/aaa/bbb’全路径匹配

解决办法

a. 改变axios的uri请求

Mock.mock('https://11.11.11.11/api/aaa/bbb', { 'code': '0', 'msg': 'success', 'data': 'dlsajfkdas' })

匹配

import axios from 'axios'
export const hungupService = () => {
  return axios({
    method: 'post',
    url: 'https://11.11.11.11/api/aaa/bbb'
  })
}

b. 改变mockjs监听的数据

Mock.mock('api/aaa/bbb', { 'code': '0', 'msg': 'success', 'data': 'dlsajfkdas' })

匹配

import axios from 'axios'
export const hungupService = () => {
  return axios({
    method: 'post',
    url: 'api/aaa/bbb'
  })
}

axios request请求拦截?mock地址到底几何?

问题描述

由于公司的请求已经被封装,默认会统一管理上下文,即业务只需要管理 api 地址,通过拦截器统一添加上下文,如果根据上面的案例来定义mock的URL,则拦截不到请求

  1. 定义拦截器
// 请求拦截器
http.interceptors.request.use(function (config) {
  config.baseURL = 'myproject'
  return config
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error)
})
  1. 定义的mock URL 地址
Mock.mock('api/aaa/bbb', { 'code': '0', 'msg': 'success', 'data': 'dlsajfkdas' })
  1. 发送请求的地址
import axios from 'axios'
export const hungupService = () => {
  return axios({
    method: 'post',
    url: 'api/aaa/bbb'
  })
}
  1. 结论现象
    mock无法拦截到定义的URL 地址

  2. 问题分析

    • 因为axios在发送请求的时候,首先被定义的拦截函数拦截,会自动的在请求前面添加“上下文”(工程名),导致URL地址改变
    • 由于URL在前面添加了上下文,导致mockjs认为我们没有定义mockURL, 因此就放过了这个请求,从而向服务器获取
  3. 解决办法 —— 定义的mockURL统一添加上下文

import Mock from 'mockjs'
const ctx = process.env.VUE_APP_CONTEXT
// 获取组织接口
Mock.mock(`/${ctx}/api/aaa/bbb', { 'code': '0', 'msg': 'success', 'data': 'dlsajfkdas' })

八、源码探究

8.1 Mock.mock 函数的多样性(自由选择请求方式)

// 避免循环依赖
if (XHR) XHR.Mock = Mock
console.log(XHR)
debugger
/*
    * Mock.mock( template )
    * Mock.mock( function() )
    * Mock.mock( rurl, template )
    * Mock.mock( rurl, function(options) )
    * Mock.mock( rurl, rtype, template )
    * Mock.mock( rurl, rtype, function(options) )

    根据数据模板生成模拟数据。
*/
Mock.mock = function(rurl, rtype, template) {
    // Mock.mock(template)
    if (arguments.length === 1) {
        return Handler.gen(rurl)
    }
    // Mock.mock(rurl, template)
    if (arguments.length === 2) {
        template = rtype
        rtype = undefined
    }
    // 拦截 XHR
    if (XHR) window.XMLHttpRequest = XHR
    console.log(rurl)
    debugger
    Mock._mocked[rurl + (rtype || '')] = {
        rurl: rurl,
        rtype: rtype,
        template: template
    }
    return Mock
}

劫持 XMLHttpRequest

原生 XMLHttpRequest 发送ajax请求的步骤

  1. 创建 XMLHttpRequest 对象
  2. 调用 open 方法,确定请求的方式 和 URL 地址
  3. 调用 send 方法发送请求
// 原生ajax实现,非常的简单
function ajax() {
    var xhr = new XMLHttpRequest();
    xhr.open('get', 'http:localhost:8080/readNum');
    xhr.send();
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            console.log('success', xhr.responseText);
        } else {
            console.log('error', xhr.responseText);
        }
    }
}

Mock.xhr 劫持的过程

按照原生ajax 发送请求的过程,mock也做了相同的步骤,根据步骤我找出关键代码

  1. 创建 XMLHttpRequest 对象
function MockXMLHttpRequest() {
    // 初始化 custom 对象,用于存储自定义属性
    this.custom = {
        events: {},
        requestHeaders: {},
        responseHeaders: {}
    }
    console.log("我是第一步")
}
  1. 调用 open 方法,确定请求的方式 和 URL 地址
// 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 that = this
    console.log("我是第二步")
    debugger

    Util.extend(this.custom, {
        method: method,
        url: url,
        async: typeof async === 'boolean' ? async : true,
        username: username,
        password: password,
        options: {
            url: url,
            type: method
        }
    })

    this.custom.timeout = function(timeout) {
        if (typeof timeout === 'number') return timeout
        if (typeof timeout === 'string' && !~timeout.indexOf('-')) return parseInt(timeout, 10)
        if (typeof timeout === 'string' && ~timeout.indexOf('-')) {
            var tmp = timeout.split('-')
            var min = parseInt(tmp[0], 10)
            var max = parseInt(tmp[1], 10)
            return Math.round(Math.random() * (max - min)) + min
        }
    }(MockXMLHttpRequest._settings.timeout)

    // 查找与请求参数匹配的数据模板 —— 查找是否有定义的mock数据
    var item = find(this.custom.options)

    function handle(event) {
        // 同步属性 NativeXMLHttpRequest => MockXMLHttpRequest
        for (var i = 0; i < XHR_RESPONSE_PROPERTIES.length; i++) {
            try {
                that[XHR_RESPONSE_PROPERTIES[i]] = xhr[XHR_RESPONSE_PROPERTIES[i]]
            } catch (e) {}
        }
        // 触发 MockXMLHttpRequest 上的同名事件
        that.dispatchEvent(new Event(event.type /*, false, false, that*/ ))
    }

    // 如果未找到匹配的数据模板,则采用原生 XHR 发送请求。
    if (!item) {
        // 创建原生 XHR 对象,调用原生 open(),监听所有原生事件
        var xhr = createNativeXMLHttpRequest()
        this.custom.xhr = xhr

        // 初始化所有事件,用于监听原生 XHR 对象的事件
        for (var i = 0; i < XHR_EVENTS.length; i++) {
            xhr.addEventListener(XHR_EVENTS[i], handle)
        }

        // xhr.open()
        if (username) xhr.open(method, url, async, username, password)
        else xhr.open(method, url, async)

        // 同步属性 MockXMLHttpRequest => NativeXMLHttpRequest
        for (var j = 0; j < XHR_REQUEST_PROPERTIES.length; j++) {
            try {
                xhr[XHR_REQUEST_PROPERTIES[j]] = that[XHR_REQUEST_PROPERTIES[j]]
            } catch (e) {}
        }

        return
    }

    // 找到了匹配的数据模板,开始拦截 XHR 请求
    this.match = true
    this.custom.template = item
    this.readyState = MockXMLHttpRequest.OPENED
    this.dispatchEvent(new Event('readystatechange' /*, false, false, this*/ ))
},

根据find方法判断是否用户自定义了mock数据的URL地址

// 查找与请求参数匹配的数据模板:URL,Type
function find(options) {
    console.log("我是第三步")

    for (var sUrlType in MockXMLHttpRequest.Mock._mocked) {
        var item = MockXMLHttpRequest.Mock._mocked[sUrlType]
        if (
            (!item.rurl || match(item.rurl, options.url)) &&
            (!item.rtype || match(item.rtype, options.type.toLowerCase()))
        ) {
            // console.log('[mock]', options.url, '>', item.rurl)
            return item
        }
    }

    function match(expected, actual) {
        if (Util.type(expected) === 'string') {
            return expected === actual
        }
        if (Util.type(expected) === 'regexp') {
            return expected.test(actual)
        }
    }

}
  1. 调用 send 方法发送请求
// https://xhr.spec.whatwg.org/#the-send()-method
// Initiates the request.
send: function send(data) {
    var that = this
    this.custom.options.body = data

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

    // 拦截 XHR

    // X-Requested-With header
    this.setRequestHeader('X-Requested-With', 'MockXMLHttpRequest')

    // loadstart The fetch initiates.
    this.dispatchEvent(new Event('loadstart' /*, false, false, this*/ ))

    if (this.custom.async) setTimeout(done, this.custom.timeout) // 异步
    else 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*/ ));
    }
},
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值