两个项目实例+常用语法解析带你掌握Mock.js✨

6,709 阅读5分钟

Mock.js简介

相信各位刚参加实习或工作的前端小伙伴们会遇到这样一个问题:当我们完成了项目静态页面的制作,这时就差后端提供接口给我们请求并展示数据了,可后端同学这时却说接口还没完成。难道我们现在就只能干等着吗?

有些同学可能会说:可以先在本地写死数据进行展示,等接口出来了,再把它们删除。由于本人是刚参加实习不久的一个非科班菜鸟,所以一开始我也是这样做的(狗头),但是这样做无疑增加了很多没必要的重复性操作,等接口出来后还需要花费精力将他们删除,严重降低了开发效率。

所以,这时候就可以考虑前端搭建web server自己模拟假数据,接下来就是我们本篇文章的主角:mockjs可以用它来生成随机数据,拦截 Ajax 请求

Mock.js官网:mockjs.com/

下图就是mockjs官网展示的它的特点:

image.png

开始&初始化

在这里我是结合vue脚手架搭建的项目对mockjs进行使用。

# 使用 axios 发送 ajax 请求
npm install axios --save 

# 使用 mockjs 产生随机数据 
npm install mockjs --save-dev

然后可以在src文件夹下新建一个文件夹mock,在文件夹下新建一个index.js文件,我们模拟接口数据的代码操作就是写在这里。

// 引入mock模块
import Mock from 'mockjs'

// 模拟接口数据的具体代码
...

main.js中导入mock

import Vue from 'vue'
import App from './App.vue'
// 导入mock
import './mock/index.js'

Vue.config.productionTip = false

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

这样,就完成了mockjs的安装和初始化啦!接下来让我们看看它的具体语法吧。

语法规范

Mock.js 的语法规范包括两部分:

  1. 数据模板定义规范(Data Template Definition,DTD)
  2. 数据占位符定义规范(Data Placeholder Definition,DPD)

1.数据模板定义规范DTD

数据模板中的每个属性由 3 部分构成:属性名、生成规则、属性值:

// 属性名   name
// 生成规则 rule
// 属性值   value
'name|rule': value
  • 属性名和生成规则之间用竖线 | 分隔。
  • 生成规则是可选的。
  • 生成规则有 7 种格式:
    1. 'name|min-max': value
    2. 'name|count': value
    3. 'name|min-max.dmin-dmax': value
    4. 'name|min-max.dcount': value
    5. 'name|count.dmin-dmax': value
    6. 'name|count.dcount': value
    7. 'name|+step': value
  • 生成规则的含义需要依赖属性值的类型才能确定。
  • 属性值中可以含有 @占位符
  • 属性值还指定了最终值的初始值和类型。

示例:

(1)属性值是字符串 String

  1. 'name|min-max': string

    通过重复 string 生成一个字符串,重复次数大于等于 min,小于等于 max

  2. 'name|count': string

    通过重复 string 生成一个字符串,重复次数等于 count

(2) 属性值是数字 Number

  1. 'name|+1': number

    属性值自动加 1,初始值为 number

  2. 'name|min-max': number

    生成一个大于等于 min、小于等于 max 的整数,属性值 number 只是用来确定类型。

  3. 'name|min-max.dmin-dmax': number

    生成一个浮点数,整数部分大于等于 min、小于等于 max,小数部分保留 dmin 到 dmax 位。

Mock.mock({
    'number1|1-100.1-10': 1,
    'number2|123.1-10': 1,
    'number3|123.3': 1,
    'number4|123.10': 1.123
})
// =>
{
    "number1": 12.92,
    "number2": 123.51,
    "number3": 123.777,
    "number4": 123.1231091814
}

(3)属性值是布尔型 Boolean

  1. 'name|1': boolean

    随机生成一个布尔值,值为 true 的概率是 1/2,值为 false 的概率同样是 1/2。

  2. 'name|min-max': value

    随机生成一个布尔值,值为 value 的概率是 min / (min + max),值为 !value 的概率是 max / (min + max)

(4)属性值是对象 Object

  1. 'name|count': object

    从属性值 object 中随机选取 count 个属性。

  2. 'name|min-max': object

    从属性值 object 中随机选取 min 到 max 个属性。

(5)属性值是数组 Array

  1. 'name|1': array

    从属性值 array 中随机选取 1 个元素,作为最终值。

  2. 'name|+1': array

    从属性值 array 中顺序选取 1 个元素,作为最终值。

  3. 'name|min-max': array

    通过重复属性值 array 生成一个新数组,重复次数大于等于 min,小于等于 max

  4. 'name|count': array

    通过重复属性值 array 生成一个新数组,重复次数为 count

(6)属性值是函数 Function

  1. 'name': function

    执行函数 function,取其返回值作为最终的属性值,函数的上下文为属性 'name' 所在的对象。

(7)属性值是正则表达式 RegExp

  1. 'name': regexp

    根据正则表达式 regexp 反向生成可以匹配它的字符串。用于生成自定义格式的字符串。

Mock.mock({
    'regexp1': /[a-z][A-Z][0-9]/,
    'regexp2': /\w\W\s\S\d\D/,
    'regexp3': /\d{5,10}/
})
// =>
{
    "regexp1": "pJ7",
    "regexp2": "F)\fp1G",
    "regexp3": "561659409"
}

2.数据占位符定义规范DPD

占位符只是在属性值字符串中占个位置,并不出现在最终的属性值中。

占位符的格式为:

@占位符
@占位符(参数 [, 参数])

注意:

  1. 用 @ 来标识其后的字符串是占位符。
  2. 占位符引用的是 Mock.Random 中的方法。
  3. 通过 Mock.Random.extend() 来扩展自定义占位符。
  4. 占位符也可以引用数据模板中的属性。
  5. 占位符会优先引用数据模板中的属性。
  6. 占位符支持相对路径和绝对路径。
Mock.mock({
    name: {
        first: '@FIRST',
        middle: '@FIRST',
        last: '@LAST',
        full: '@first @middle @last'
    }
})
// =>
{
    "name": {
        "first": "Charles",
        "middle": "Brenda",
        "last": "Lopez",
        "full": "Charles Brenda Lopez"
    }
}

Mock.Random 中的方法与数据模板的 @占位符 一 一对应,在需要时还可以为 Mock.Random 扩展方法,然后在数据模板中通过 @扩展方法 引用。例如:

Random.extend({
    constellation: function(date) {
        var constellations = ['白羊座', '双子座', '巨蟹座', '狮子座', '天秤座', '天蝎座', '射手座', '水瓶座']
        return this.pick(constellations)
    }
})
Random.constellation()
// => "水瓶座"
Mock.mock('@CONSTELLATION')
// => "天蝎座"
Mock.mock({
    constellation: '@CONSTELLATION'
})
// => { constellation: "射手座" }

3.Mock.mock

调用Mock.mock( rurl?, rtype?, template|function( options ) )生成模拟数据

(1) Mock.mock( template )

根据数据模板生成模拟数据。

(2) Mock.mock( rurl, template )

记录数据模板。当拦截到匹配 rurl 的 Ajax 请求时,将根据数据模板 template 生成模拟数据,并作为响应数据返回。

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

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

(4) Mock.mock( rurl, rtype, template )

记录数据模板。当拦截到匹配 rurl 和 rtype 的 Ajax 请求时,将根据数据模板 template 生成模拟数据,并作为响应数据返回。

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

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

  • rurl

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

  • rtype

可选。表示需要拦截的 Ajax 请求类型。例如 GETPOSTPUTDELETE 等。

  • template

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

  • function(options)

可选。表示用于生成响应数据的函数。

  • options

指向本次请求的 Ajax 选项集,含有 urltype 和 body 三个属性

常用基础语法举例

1. 生成字符串

  • 生成指定次数字符串
const data = Mock.mock({
    "string | 3": "aa"  // 生成"aaaaaa"
})
  • 生成指定范围长度字符串
const data = Mock.mock({
    "string | 1-8": "a" // 随机生成1-8个长度的"a"
})

2.生成文本

  • 生成一个随机字符串
const data = Mock.mock({
    "string": "@cword"  
}) 

注意:在@符号的后面以c开头的一般表示的是中文,如上方的cword就表示一个随机的汉字,如果是word的话就是一个随机的英文单词。

  • 生成指定长度和范围
const data = Mock.mock({
    string: "@cword(1)"
    str : "@cword(10,15)"
})

3.生成标题和句子

  • 生成标题和句子
const data = Mock.mock({
    title: "@ctitle"
    sentence: "@csentence"
})
  • 生成指定长度的标题和句子
const data = Mock.mock({
    title: "@ctitle(8)"
    sentence: "@csentence(50)"
})
  • 生成指定范围的标题和句子
const data = Mock.mock({
    title: "@ctitle(5,8)"
    sentence: "@csentence(50,100)"
})

4.生成段落

  • 随机生成段落
const data = Mock.mock({
  content: "@cparagraph()"
})

5.生成数字

  • 生成指定数字
const data = Mock.mock({
    "number|80": 1
})
  • 生成范围数字
const data = Mock.mock({
    "number|1-99": 1
})

6.生成自增id

  • 随机生成标识
const data = Mock.mock({
    id: "@increment(1)" // 生成递增量为1的id
})

7.生成姓名-地址-身份证

  • 随机生成姓名-地址-身份证
const data = Mock.mock({
    name: "@cname()"
    idCard: "@id()"
    address: "@city(true)"
    // @city如果带true参数,则还会自带省份
})

8.随机生成图片

  • 生成图片:@image(size?, background?, foreground?, format?, text?)
  • size:图片大小
[    '300*250','250*250','240*400','336*280'    '180*150','720*300','468*60','234*60'    '388*31','250*250','240*400','120*40'    '125*125','250*250','240*400','336*280']
  • background:图片背景色
  • foreground:图片前景色
  • format:图片格式
  • text:图片文字

9.生成时间

  • @Date
  • 生成指定格式时间:@date(yyyy-MM-dd hh:mm:ss)

指定数组返回的参数

  • 指定长度:'date|5'
  • 指定范围: 'data|5-10'
const data = Mock.mock({
'list|50-99':[
        {
            name:'@cname'
            address:'@city(true)'
            id:'@increment()'
        }	
    ]
})

更多具体示例参见:mockjs.com/examples.ht…

下面,我用两个具体的结合Vue的增删改查的小demo来演示一下mockjs的使用吧。

具体实例一

首先,我们在main.js中导入并挂载axios,这样我们就能在每个组件中通过$http请求接口数据。

import Vue from 'vue' 
import App from './App.vue' 
// 导入mock 
import './mock/index.js' 
import axios from 'axios'
// 挂载到Vue原型上
Vue.prototype.$http = axios
Vue.config.productionTip = false 

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

接下来,我们就是在之前创建的mock文件夹下的index.js中写模拟数据啦

import Mock from 'mockjs'
// 导入 模拟假数据的包
import { Random } from 'mockjs'

// 创建自定义 Mock 函数
Random.extend({
  // 自定义函数名: function 函数
  fruit: function() {
    const arr = ['榴莲', '波罗蜜', '椰子', '苹果', '菠萝', '释迦']
    return this.pick(arr)
  }
})

// 获取商品列表
Mock.mock('/api/goodslist', 'get', {
  status: 200,
  message: '获取商品列表成功!',
  'data|5-10': [
    {
      id: '@increment(1)',      // 自增的Id值
      // 'id|+1': 0,            // 这也是在模拟一个自增长的 Id 值
      name: '@cword(2, 8)',     // 随机生成中文字符串
      price: '@natural(2, 10)', // 自然数
      count: '@natural(100, 999)',
      img: '@dataImage(78x78)'  // 指定宽高图片
    }
  ]
})

// 添加商品
Mock.mock('/api/addgoods', 'post', function(option) {
  // 这里的 option 是请求相关的参数
  console.log(option)
  // 如果需要在返回的对象中再使用mock的语法,则需要再使用Mock.mock
  return Mock.mock({
    status: 200,
    message: '@cword(2,5)'
  })
})

// 根据Id获取商品信息
Mock.mock(/\/api\/getgoods/, 'get', function(option) {
  console.log(option)
  
  // 通过 正则 的 .exec() 函数,从字符串中提取需要的数据
  const res = /\/api\/getgoods/(\d+)/.exec(option.url)
  
  // 也可以通过字符串的split方法获取id
  // const urlId = option.url.split('/')[3]
  
  return Mock.mock({
    data: {
      id: res[1] - 0,
      // id: urlId,
      // 这里的@fruit()是前面自定义的mock函数
      name: '@fruit()',
      price: 2,
      count: 199,
      img: '@dataImage(78x78)'
    },
    status: 200,
    message: '获取商品成功!'
  })
})

然后在我们需要请求接口数据的组件中发送相应的请求

<template>
  <div id="app">
    <button @click="getGoodsList">获取商品列表</button>
    <button @click="addGoods">添加商品</button>
    <button @click="getGoodsById(9)">根据Id获取商品信息</button>
  </div>
</template>
<script>
export default {
  methods: {
    // 获取商品列表
    async getGoodsList() {
      const { data: res } = await this.$http.get('/api/goodslist')
      console.log(res)
    },
    
    // 添加商品,在这里我将添加商品的数据写死了,具体在开发中是需要获取用户输入的值进行添加
    async addGoods() {
      const { data: res } = await this.$http.post('/api/addgoods', {
        name: '菠萝',
        price: 8,
        count: 550,
        img: ''
      })
      console.log(res)
    },
    
    // 根据Id获取商品信息
    async getGoodsById(id) {
      const { data: res } = await this.$http.get(`/api/getgoods/${id}`)
      console.log(res)
    }
  }
}
</script>

在具体开发中,请求接口数据的代码肯定不止这些,当全部有关请求接口数据的代码都放在index.js下时,文件就会变得很臃肿,不便于管理和区分。其实我们可以像Vuex的模块分离那样对不同类型的请求进行模块化

比如将商品相关的模拟数据拆分到goods.js中:

import Mock from 'mockjs'

// 获取商品列表
Mock.mock('/api/goodslist', 'get', {
  ...
})

// 添加商品
Mock.mock('/api/addgoods', 'post', function(option) {
  ...
})

// 根据Id获取商品信息
Mock.mock(/\/api\/getgoods/, 'get', function(option) {
  ...
})

将自定义的mock函数的代码拆分到extends.js中:

// 导入 模拟假数据的包
import { Random } from 'mockjs'

// 创建自定义 Mock 函数
Random.extend({
  // 自定义函数名: function 函数
  fruit: function() {
    const arr = ['榴莲', '波罗蜜', '椰子', '苹果', '菠萝', '释迦']
    return this.pick(arr)
  }
})

如果还有其他模块的数据也可以放在不同的文件中,比如用户模块的代码放在user.js中,购物车模块的代码放在cart.js中等等。

最后,在入口文件index.js中集中导入即可

// 导入扩展函数
import './extends'

// 导入商品模块
import './goods'

// 导入用户模块  user.js
// 导入购物车模块  cart.js

最后在mock文件夹下的文件目录就如下图所示:

image.png

具体实例二

其实,实例二与实例一基本类似,也是对列表进行增删改查的操作,只是在实例一的基础上加上分页的功能,以及请求数据的参数不同,还有删除和添加的方式略有不同,在这里我就不进行模块划分了,直接全部写在index.js里啦

import Mock from "mockjs";

// 通过解构赋值模拟新闻列表数据
const { newsList } = Mock.mock({
  "newsList|75": [
    {
      id: "@increment",
      title: "@ctitle()",
      content: "@cparagraph(5,10)",
      img_url: "@image('50*50','#FF83FA','#FCFCFC','png','mono')",
      add_time: "@date(yyyy-MM-dd hh:mm:ss)",
    },
  ],
});
console.log(newsList);

// 封装一个从url中获取query参数的函数
// 比如从https://juejin.cn/user/4433690702123534?id=1&name=rocky中取出?后面的键值对
var getQuery = (url, name) => {
  console.log(url, name);
  
  // 判断是否有query参数
  const index = url.indexOf("?");
  if (index !== -1) {
    // 截取?后面字符串再将其以&分隔成数组:['id=1', 'name=rocky']
    const queryStrArr = url.substr(index + 1).split("&");
    
    for (var i = 0; i < queryStrArr.length; i++) {
      // 再对数组的每一项以=进行分隔成数组:[id, 1],[name, rocky]
      const itemArr = queryStrArr[i].split("=");
      console.log(itemArr);
      
      if (itemArr[0] === name) {
        // 取出键名对应的键值
        return itemArr[1];
      }
    }
  }
  return null;
};

// 获取新闻列表的数据
// 因为具体的url形式类似为/api/get/news?pageinde1&pagesize=10,所以我们这里使用正则去匹配
Mock.mock(/\/api\/get\/news/, "get", (options) => {

  // 获取分页相关参数pageindex,pagesize
  const pageindex = getQuery(options.url, "pageindex");
  const pagesize = getQuery(options.url, "pagesize");
  console.log(pageindex);
  console.log(pagesize);
  
  // 获取数据的起始位置,结束位置和总页数
  // pageindex:1 pagesize:10 返回0-9条数据  2-10(10-19) 3-10(20-29)
  const start = (pageindex - 1) * pagesize;
  const end = pagesize * pageindex;
  const totalPage = Math.ceil(newsList.length / pagesize);
  const list = pageindex > totalPage ? [] : newsList.slice(start, end);
  
  return {
    status: 200,
    message: "获取新闻列表成功",
    list: list,
    total: totalPage,
  };
});

// 添加新闻的数据
Mock.mock("/api/add/news", "post", (options) => {
  const body = JSON.parse(options.body);
  console.log(body);
  newsList.push(
    Mock.mock({
      id: "@increment",
      title: body.title,
      content: body.content,
      img_url: "@image('50*50','#FF83FA','#FCFCFC','png','mono')",
      add_time: "@date(yyyy-MM-dd hh:mm:ss)",
    })
  );
  return {
    status: 200,
    message: "添加成功",
    list: newsList,
    total: newsList.length,
  }
})

// 定义删除新闻
Mock.mock("/api/delete/news", "post", (options) => {
  console.log(options);
  const body = JSON.parse(options.body);
  console.log(body);
  const index = newsList.findIndex((item) => {
    return item.id === body.id;
  });
  newsList.splice(index, 1);
  console.log(index);
  return {
    status: 200,
    message: "删除成功",
    list: newsList,
    total: newsList.length,
  }
})
console.log(Mock)

在具体请求接口数据的组件中:

<template>
  <div>
    <div class="add">
      <input type="text" v-model="title" placeholder="输入标题" />
      <input type="text" v-model="content" placeholder="输入内容" />
      <button @click="add">添加</button>
    </div>
    <div class="news_list">
      <table>
        <tr v-for="item in list" :key="item.id">
          <td><img :src="item.img_url" alt="" /></td>
          <td>{{ item.title }}</td>
          <td>{{ item.content }}</td>
          <td>{{ item.add_time }}</td>
          <td>
             <button class="remove" @click="remove(item.id)">删除</button>
          </td> 
        </tr>
      </table>
    </div>
    <div class="pages">
      <button @click="prevPage">上一页</button>
      <button @click="nextPage">下一页</button>
    </div>
  </div>
</template>

<script>
import axios from "axios";
export default {
  data() {
    return {
      title: "",
      content: "",
      list: [],
      pageindex: 1,
      title: "",
      content: "",
    };
  },
  created() {
    this.getNewsList();
  },
  methods: {
    // 添加新闻数据
    add() {
      // if (this.title.trim() === "" || this.content.trim() === "")
      //   return alert("请添加那些新闻标题和内容");
      // console.log(this.title, this.content);
      axios.post("/api/add/news", {
          title: this.title,
          content: this.content,
        }).then((res) => {
          console.log(res);
        });
    },
    
    //获取新闻列表数据
    getNewsList() {
      axios.get("/api/get/news", {
         params: {
           pageindex: this.pageindex,
           pagesize: 10,
         },
      }).then((res) => {
         console.log(res);
         this.list = res.data.list;
      });
    },
    
    nextPage() {
      this.pageindex++;
      this.getNewsList();
    },
    prevPage() {
      this.pageindex--;
      this.getNewsList();
    },
    
    // 删除新闻
    remove(id) {
      // console.log(id);
      axios.post("/api/delete/news", {id})
      .then((res) => {
        console.log(res);
      });
    }
  }
}
</script>

参考

Mock.js官网:mockjs.com/
B站mock相关教程:
www.bilibili.com/video/BV1v7… www.bilibili.com/video/BV1Tt…