前端项目中使用Mock的方案

835 阅读9分钟

在项目开发中,前后端分离架构已经成为主流,前端开发进度往往依赖于后端接口的完成度,当后端接口尚未完成时,前端就会由于空数据而写死数据或使用 Express 等 Node 框架来自己造数据从而渲染UI界面,这往往会导致开发成本以及后期联调成本的增高,在此情形下,Mock 的出现,很好的解决了这一问题,它十分方便的模拟中开发中常见的场景,能够轻松的实现增删改查等常规操作,以下是我总结出来的使用方案:

安装Mock

官网地址:mockjs.com/

pmpm i mockjs

pnpm install @types/mockjs -D

语法规则

数据模板定义规范

数据模板中每个属性由 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

具体规则说明请参考官方文档:github.com/nuysoft/Moc…

示例如下:

属性值是字符串

// 语法:'name|min-max': string
// 重复生成一个字符串 *,重复的次数介于 [1,10] 之间
Mock.mock({ 'name|1-10': '*' }) // { name: '*****' }

// 语法:'name|count': string
// 重复生成一个字符串坤,重复的次数等于 2
Mock.mock({ 'name|2': '坤' }) // { name: '坤坤' }

属性值是数字

// 语法:'name|+1': number
// id 属性自增 +1,初始值为 1
Mock.mock({ 'id|+1': 1 }) // 1

// 语法:'name|min-max': number
// 随机生成一个介于 [18,30] 之间的数字
Mock.mock({ 'age|18-30': 18 }) // 26

// 语法:'name|min-max.dmin-dmax': number
// 随机生成一个整数部分介于 [1,100] 之间的数字,小数点后保留 [1,2] 位
Mock.mock({ 'num|1-100.1-2': 10 }) // { num: 73.42 }

// 随机生成一个整数部分固定为 88,小数点后保留 [1,10] 位
Mock.mock({ 'num|88.1-10': 10 }) // { num: 88.1246 }

// 随机生成一个整数部分固定为 66,小数点后保留 2 位
Mock.mock({ 'num|66.2': 10 }) // { num: 66.91 }

属性值是布尔

// 语法:'name|1': boolean
// 随机生成一个布尔值,值为 true 或 false
Mock.mock({ 'bool|1': true }) // { bool: false }

// 语法:'name|min-max': value
// 随机生成一个布尔值,值为 true 的概率为 1/5(min/(min+max)),为 false 的概率为 4/5(max/(min+max))
Mock.mock({ 'bool|1-4': true }) // { bool: false }

属性值是对象

const obj = { name: '坤坤', age: 18, hobby: ['唱', '跳', 'rap'] }

// 语法:'name|count': object
// 从 obj 对象中,随机抽取 2 属性生成一个新对象
Mock.mock({ 'user|2': obj }) // { user: { age: 18, name: '坤坤' } }

// 语法:'name|min-max': object
// 从 obj 对象中,随机抽取 1-3 个属性生成一个新对象
Mock.mock({ 'user|1-3': obj }) // { user: { age: 18, name: '坤坤', hobby: [ '唱', '跳', 'rap' ] } }

属性值是数组

const arr = ['刘一', '陈二', '张三', '李四', '王五', '赵六']

// 语法:'name|1': array
// 随机从 arr 数组中随机抽取 1 个元素作为最终值
Mock.mock({ 'user|1': arr }) // { name: '张三' }

// 语法:'name|+1': array
// 从 arr 数组中,按照顺序,步长为 2 的规则抽取元素作为最终值,默认初始索引为 0,当索引超过数据长度时,会通过取模运算回到起点
// 索引计算公式:(初始索引 + 步长) % 数据长度
Mock.mock({ 'name|+2': arr }) // { name: '刘一' }
Mock.mock({ 'list|3': [{ 'name|+2': arr }] }) // { list: [ { name: '张三' }, { name: '王五' }, { name: '刘一' } ] }

// 语法:'name|min-max': array
// 重复数组值生成一个新数组,重复次数介于 [1,6] 之间
Mock.mock({ 'nameList|1-6': arr }) // { nameList: ['刘一', '陈二','张三', '李四','王五', '赵六','刘一', '陈二','张三', '李四','王五', '赵六'] }

// 语法:'name|count': array
// 重复数组值 2 次生成一个新数组
Mock.mock({ 'nameList|2': arr }) // { nameList: ['刘一', '陈二','张三', '李四','王五', '赵六','刘一', '陈二','张三', '李四','王五', '赵六'] }

属性值是函数

// 语法:'name': function
// 取函数的返回值作为属性值
Mock.mock({ name: () => '坤坤' }) // { name: '坤坤' }

属性值是正则表达式

// 语法:'name': regexp
// 根据正则表达式反向生成可以匹配的字符串
Mock.mock({ name: /[a-z]{3}/ }) // { name: 'bsj' }

数据占位符定义规范

占位符在属性值中占据位置,并不会出现在最终值中,占位符引用的是 Mock.Random() 中的方法

语法:@占位符@占位符(参数 [, 参数])

列举几个常用的占位符:

数据类型

// 布尔值 Mock.Random.boolean()
Mock.mock({ bool: '@boolean' })

// 自然数(大于等于0的整数)Mock.Random.natural()
Mock.mock({ num: '@natural' })

// 整数 Mock.Random.integer()
Mock.mock({ num: '@integer' })

// 浮点数 Mock.Random.float()
Mock.mock({ num: '@float(1,100,1,3)' })

// 字符串 Mock.Random.character()、Mock.Random.string()
Mock.mock({ str: '@character("upper")' })
Mock.mock({ str: '@string(1,10)' }))

// 整型数组 Mock.Random.range()
Mock.mock({ arr: '@range(0,10,2)' })

日期

// 日期 Mock.Random.date()
Mock.mock({ date: '@date("yyyy-MM-dd")' })

// 时间 Mock.Random.time()
Mock.mock({ time: '@time("HH:mm:ss")' })

// 日期和时间 Mock.Random.datetime()
Mock.mock({ datetime: '@datetime("yyyy-MM-dd HH:mm:ss")' })

// 当前日期时间 Mock.Random.now()
Mock.mock({ now: '@now' })

图片

// 图片大小、背景色、字体色、图片格式、图片名称 Mock.Random.image()
console.log(Mock.mock({ img: '@image("200x100", "#fff", "#000", "png", "img")' }))

颜色

// 格式为 '#RRGGBB' Mock.Random.color()
Mock.mock({ color: '@color' })

// 格式为 '#RRGGBB' Mock.Random.hex()
Mock.mock({ color: '@hex' })

// 格式为 'rgb(r, g, b)' Mock.Random.rgb()
Mock.mock({ color: '@rgb' })

// 格式为 'rgba(r, g, b, a)' Mock.Random.rgba()
Mock.mock({ color: '@rgba' })

// 格式为 'hsl(h, s, l)' Mock.Random.hsl()
Mock.mock({ color: '@hsl' })

文本

// 标题:@ctitle:中文标题 @title:英文标题 Mock.Random.ctitle()、Mock.Random.title()
Mock.mock({ title: '@ctitle(5,10)' })

// 段落:@cparagraph:中文段落 @paragraph:英文段落 Mock.Random.cparagraph()、Mock.Random.paragraph()
Mock.mock({ paragraph: '@cparagraph' })

// 文本:@csentence:中文句子 @sentence:英文句子 Mock.Random.csentence()、Mock.Random.sentence()
Mock.mock({ sentence: '@csentence' })

姓名

// 姓名:@cname:中文名字 @name:英文名字 Mock.Random.cname()、Mock.Random.name()
Mock.mock({ name: '@name' })

// 姓:@cfirst:中文姓 @last:英文姓 Mock.Random.cfirst()、Mock.Random.last()
Mock.mock({ firstName: '@cfirst' })

// 名:@clast:中文名 @first:英文名 Mock.Random.clast()、Mock.Random.first()
Mock.mock({ lastName: '@clast' })

Web

// URL:@url Mock.Random.url()
Mock.mock({ url: '@url("https")' })

// URL:@protocol 协议 Mock.Random.protocol()
Mock.mock({ protocol: '@protocol' })

// 域名:@domain Mock.Random.domain()
Mock.mock({ domain: '@domain' })

// 邮箱:@email Mock.Random.email()
Mock.mock({ email: '@email' })

// ip地址:@ip Mock.Random.ip()
Mock.mock({ ip: '@ip' })

地址

// 区域: @region Mock.Random.region()
Mock.mock({ region: '@region' })

// 省份: @province Mock.Random.province()
Mock.mock({ province: '@province' })

// 城市: @city Mock.Random.city()
Mock.mock({ city: '@city' })

// 县: @county Mock.Random.county()
// 传递参数:true,返回带省市县名的地址
Mock.mock({ county: '@county(true)' })

// 邮编: @zip Mock.Random.zip()
Mock.mock({ zip: '@zip' })

其他

// 首字母大写 @capitalize Mock.Random.capitalize()
Mock.mock({ str: '@capitalize("hello")' })

// 字符串大写 @upper Mock.Random.upper()
Mock.mock({ str: '@upper("hello")' })

// 字符串小写 @lower Mock.Random.lower()
Mock.mock({ str: '@lower("HELLO")' })

// 从数组中随机选取一个元素并返回 @pick Mock.Random.pick()
Mock.mock({ user: '@pick(["刘一", "陈二","张三", "李四","王五", "赵六"])' })

// 打乱数组中元素的顺序并返回 @shuffle Mock.Random.shuffle()
Mock.mock({ user: '@shuffle(["刘一", "陈二","张三", "李四","王五", "赵六"])' })

// 随机生成一个 guid,@guid Mock.Random.guid()
Mock.mock({ guid: '@guid' })

// 随机生成一个 18 位的 id,@id Mock.Random.id()
Mock.mock({ id: '@id' })

模拟请求

基本使用

在项目根目录下,新建 mock 文件夹,新建 user.ts 文件,内容如下:

mock/user.ts

import Mock from 'mockjs'
// 设置请求响应的时间,单位毫秒
Mock.setup({ timeout: '300' })

Mock.mock('/api/getUserInfo', 'get', {
  code: 200,
  data: {
    id: '@id',
    name: '@cname',
    age: '@integer(18, 60)',
    sex: '@pick(男,女)',
    email: '@email',
    address: '@county(true)'
  },
  msg: '获取成功!'
})

main.ts 文件中引入此文件

src/main.ts

import '../mock/user'

在相应的 vue 文件中向此地址发送请求

<script setup lang="ts">
  import axios from 'axios'

  async function getUserInfo() {
    const { data: res } = await axios.get('/api/getUserInfo')
    console.log(res.data) 
    /* 
    {
      "id": "990000197505125169",
      "name": "余军",
      "age": 38,
      "sex": "男",
      "email": "e.mckuflk@mqwyjmgp.sn",
      "address": "河南省 洛阳市 老城区"
    } 
    */
  }
  getUserInfo()
</script>

vite-plugin-mock

仓库地址:github.com/vbenjs/vite…

vite-plugin-mock 是一个专门为 vite 设计的插件,能够同时支持开发环境跟生产环境,简化了前端开发中模拟接口和数据生成流程,以下是它的基本使用:

pnpm i vite-plugin-mock -D

vite.config.ts 中进行配置

vite.config.ts

import { UserConfigExport, ConfigEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import { viteMockServe } from 'vite-plugin-mock'

// https://vitejs.dev/config/
export default ({ command }: ConfigEnv): UserConfigExport => {
  return {
    plugins: [
      vue(),
      viteMockServe({
        // 忽略以 _ 开头的文件
        ignore: /^\_/,
        // 模拟数据存放目录
        mockPath: 'mock',
        // 是否启用 mock 功能
        enable: true,
        // 是否在控制台显示请求日志
        logger: true
      })
    ],
  }
}

在项目根目录中新建 mock 文件夹,建立 user.ts 文件,内容如下:

import { MockMethod } from 'vite-plugin-mock'
export default [
  {
    url: '/api/getToken',
    method: 'get',
    response: () => {
      return {
        code: 200,
        data: {
          token: 'token'
        },
        msg: '登录成功!'
      }
    }
  },
  {
    url: '/api/getUserInfo',
    method: 'get',
    response: () => {
      return {
        code: 200,
        data: {
          id: '@id',
          name: '@cname',
          age: '@integer(18, 60)',
          sex: '@pick(男,女)',
          email: '@email',
          address: '@county(true)',
          avatar: '@image(200x200, @color, @cname)'
        },
        msg: '获取成功!'
      }
    }
  }
] as MockMethod[]

发送请求进行测试,是能够拿到数据的

<template>
  <div>
    <button @click="getToken">获取token</button>
    <button @click="getUserInfo">获取用户信息</button>
  </div>
</template>

<script setup lang="ts">
  import axios from 'axios'

  async function getUserInfo() {
    const { data: res } = await axios.get('/api/getUserInfo')
    console.log('userInfo', res.data)
  }

  async function getToken() {
    const { data: res } = await axios.get('/api/getToken')
    console.log('token', res.data)
  }

  getToken()
  getUserInfo()
</script>

<style scoped></style>

在生产环境中使用 mock,则需要进行如下配置:

mock 文件夹下新建 _createProductionServer.ts 文件,文件内容如下:

mock/_createProductionServer.ts

import { createProdMockServer } from 'vite-plugin-mock/client'
const modules: any = import.meta.glob('./**/*.ts', { eager: true })

const mockModules: any[] = []
Object.keys(modules).forEach((key) => {
  if (key.includes('/_')) {
    return
  }
  mockModules.push(...modules[key].default)
})

export function setupProdMockServer() {
  createProdMockServer(mockModules)
}

main.ts 引入使用

main.ts

import { createApp } from 'vue'
import './styles/index.css'
import App from './App.vue'

createApp(App).mount('#app')

if (import.meta.env.PROD) {
  import('../mock/_createProductionServer').then(({ setupProdMockServer }) => {
    setupProdMockServer()
  })
}

接下来我们进行打包测试,运行如下命令即可:

"scripts": {
  "build": "vite build",
  "preview": "npm run build && vite preview",
},
# 此命令会进行打包,打包结束后会生成一个预览链接:http://localhost:4173/
pnpm run preview

进行测试,小编发现了一个小问题,在生产环境下主动触发接口,会拿不到数据,通过事件调用触发才可以拿到,不知道大家有没有发现这个问题,在翻阅官方 githubissue 后,确实有不少人提出在生产环境存在一些问题,需在生产环境下慎用该插件。

vite-plugin-fake-server

仓库地址:github.com/condorherob…

此插件是 vite-plugin-mock 插件的平替,使用方式如下:

pnpm i vite-plugin-fake-server -D

vite.config.ts 中进行配置

vite.config.ts

import { UserConfigExport, ConfigEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import { vitePluginFakeServer } from 'vite-plugin-fake-server'

// https://vitejs.dev/config/
export default ({ command }: ConfigEnv): UserConfigExport => {
  return {
    plugins: [
      vue(),
      vitePluginFakeServer({
        // mock文件夹下的所有的 *.mock.ts 文件,都会被加载
        include: 'mock',
        infixName: 'mock',
        // 开发环境是否启用
        enableDev: true,
        // 生产环境是否启用
        enableProd: true,
        // 是否在控制台显示请求日志
        logger: true
      })
    ],
  }
}

在项目跟目录下新建 mock 文件,建立 user.mock.ts 文件,内容如下:

mock/user.mock.ts

import { defineFakeRoute } from 'vite-plugin-fake-server/client'
import Mock from 'mockjs'

export default defineFakeRoute([
  {
    url: '/api/getToken',
    method: 'get',
    response: () => {
      return {
        code: 200,
        data: {
          token: 'token'
        },
        msg: '登录成功!'
      }
    }
  },
  {
    url: '/api/getUserInfo',
    method: 'get',
    response: () => {
      return {
        code: 200,
        data: Mock.mock({
          id: '@id',
          name: '@cname',
          age: '@integer(18, 60)',
          sex: '@pick(男,女)',
          email: '@email',
          address: '@county(true)',
          avatar: '@image(200x200, @color, @cname)'
        })
      }
    }
  }
])

发送请求进行测试,获取数据成功,测试代码同 vite-plugin-mock,这里不在描述

接着进行打包预览测试,命令同上

pnpm run preview

进行测试发现无论是主动触发还是事件调用都能拿到数据,至此,在前端项目中使用 Mock 这篇文章就到此结束啦~~~