Mock.js简介
相信各位刚参加实习或工作的前端小伙伴们会遇到这样一个问题:当我们完成了项目静态页面的制作,这时就差后端提供接口给我们请求并展示数据了,可后端同学这时却说接口还没完成。难道我们现在就只能干等着吗?
有些同学可能会说:可以先在本地写死数据进行展示,等接口出来了,再把它们删除。由于本人是刚参加实习不久的一个非科班菜鸟,所以一开始我也是这样做的(狗头),但是这样做无疑增加了很多没必要的重复性操作,等接口出来后还需要花费精力将他们删除,严重降低了开发效率。
所以,这时候就可以考虑前端搭建web server自己模拟假数据,接下来就是我们本篇文章的主角:mockjs
。可以用它来生成随机数据,拦截 Ajax 请求。
Mock.js官网:mockjs.com/
下图就是mockjs官网
展示的它的特点:
开始&初始化
在这里我是结合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
的语法规范包括两部分:
- 数据模板定义规范
(Data Template Definition,DTD)
- 数据占位符定义规范
(Data Placeholder Definition,DPD)
1.数据模板定义规范DTD
数据模板中的每个属性由 3 部分构成:属性名、生成规则、属性值:
// 属性名 name
// 生成规则 rule
// 属性值 value
'name|rule': value
- 属性名和生成规则之间用竖线
|
分隔。 - 生成规则是可选的。
- 生成规则有 7 种格式:
'name|min-max': value
'name|count': value
'name|min-max.dmin-dmax': value
'name|min-max.dcount': value
'name|count.dmin-dmax': value
'name|count.dcount': value
'name|+step': value
- 生成规则的含义需要依赖属性值的类型才能确定。
- 属性值中可以含有
@占位符
。 - 属性值还指定了最终值的初始值和类型。
示例:
(1)属性值是字符串 String
-
'name|min-max': string
通过重复
string
生成一个字符串,重复次数大于等于min
,小于等于max
。 -
'name|count': string
通过重复
string
生成一个字符串,重复次数等于count
。
(2) 属性值是数字 Number
-
'name|+1': number
属性值自动加 1,初始值为
number
。 -
'name|min-max': number
生成一个大于等于
min
、小于等于max
的整数,属性值number
只是用来确定类型。 -
'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
-
'name|1': boolean
随机生成一个布尔值,值为 true 的概率是 1/2,值为 false 的概率同样是 1/2。
-
'name|min-max': value
随机生成一个布尔值,值为
value
的概率是min / (min + max)
,值为!value
的概率是max / (min + max)
。
(4)属性值是对象 Object
-
'name|count': object
从属性值
object
中随机选取count
个属性。 -
'name|min-max': object
从属性值
object
中随机选取min
到max
个属性。
(5)属性值是数组 Array
-
'name|1': array
从属性值
array
中随机选取 1 个元素,作为最终值。 -
'name|+1': array
从属性值
array
中顺序选取 1 个元素,作为最终值。 -
'name|min-max': array
通过重复属性值
array
生成一个新数组,重复次数大于等于min
,小于等于max
。 -
'name|count': array
通过重复属性值
array
生成一个新数组,重复次数为count
。
(6)属性值是函数 Function
-
'name': function
执行函数
function
,取其返回值作为最终的属性值,函数的上下文为属性'name'
所在的对象。
(7)属性值是正则表达式 RegExp
-
'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
占位符只是在属性值字符串中占个位置,并不出现在最终的属性值中。
占位符的格式为:
@占位符
@占位符(参数 [, 参数])
注意:
- 用
@
来标识其后的字符串是占位符。 - 占位符引用的是
Mock.Random
中的方法。 - 通过
Mock.Random.extend()
来扩展自定义占位符。 - 占位符也可以引用数据模板中的属性。
- 占位符会优先引用数据模板中的属性。
- 占位符支持相对路径和绝对路径。
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 请求类型。例如 GET
、POST
、PUT
、DELETE
等。
- template
可选。表示数据模板,可以是对象或字符串。例如 { 'data|1-10':[{}] }
、'@EMAIL'
。
- function(options)
可选。表示用于生成响应数据的函数。
- options
指向本次请求的 Ajax 选项集,含有 url
、type
和 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
文件夹下的文件目录就如下图所示:
具体实例二
其实,实例二与实例一基本类似,也是对列表进行增删改查的操作,只是在实例一的基础上加上分页的功能,以及请求数据的参数不同,还有删除和添加的方式略有不同,在这里我就不进行模块划分了,直接全部写在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…