4

简介

话不多说,把手拿来。
珍爱生命,远离后台。

背景

在前后端分离模式项目开发过程中,前端的界面展示、交互逻辑往往需要后台接口数据支撑,然而万恶的后台总跟不上有我们美丽的前端小姐姐进度。
于是我们不由得仰天长叹,难道就没有即无需依赖后台又能完美展示界面数据,即能保证前端交互的完整性又不太费事儿的方法吗?
答案当然是有的~~~~ 下面就请我们的主角登场

Mockjs

介绍

Mock官网首页是这么定义的:生成随机数据,拦截 Ajax 请求。

传送门 & 示例

作用

  1. 减少开发成本

    前后端分离其实从增加了开发成本,只是在它带来的优势上可以让我们忍受这种成本,但攻克这方面成本的目标却从未停止
  2. 剥离前后端开发时的耦合性

    做完本阶段的开发详设之后,前端就可以开时本阶段的开发了,由于基础设定已经在详设阶段完成统一,前后端在开发过程中应该是可以完全独立的。但实际开发中前端往往留着某些接口回执逻辑等着与后台对接后填充,无法完全完成前端自身的界面展示及交互逻辑。而使用mockjs模拟接口及数据前端可以在最大程度上彻底分离开发时与后台的耦合成本,在开发时就基本完成全部前端逻辑编写
  3. 减少前后端接口对接时间

    前后端完成独立开发后,就进入了前后台对接阶段,很多项目组就在此阶段会花费大量的调试时间及成本。然而实际上,如果前端做完了数据及交互的逻辑,后台完成了所有接口自测,此阶段应该是非常迅速的
  4. 前端自建项目演示

    很多前端开源项目都是无后台,模拟数据演示的。例如vue-element-admin,d2-admin,wl-admin.

在项目中应用

  1. 引入js依赖

    npm install mockjs      
  2. 建一个mock文件夹来统一管理我们的mock数据
  3. 在mock文件夹下建一个demo.js尝试一下
  4. 在mock/demo.js中写下如下代码:

    import Mock from 'mockjs'
    随机生成一个20-40条的数组数据试试
    const projectList = Mock.mock({
        "list|20": [{
        'name': '@cname', // 中文名
        'account': `@word`, // 英文单词
        'phone': /1[3-9][0-9]{9}/, // 正则模式
        'deptName': Mock.mock('@cword(2,4)'), // 随机2-4字中文单词
        'id': '@guid', // guid
       }]
     })

    有多种模式可选,具体见官方示例。

    有了数据之后,我们怎么把数据模拟通过接口返回呢
    export default [
        {
            url: '/Api/Project/GetList',
            type: 'post',
            response: (res) => {
                let _fileter_list = [];
                if(res.body.key){
                    let _fileter_list = projectList.fileter(i => i.name == res.body.key)
                }
                
                // 没错,你应该已经猜到了,res.body就是我们传入到接口的数据,我们可以在这里做些逻辑操作
                // res包含完整的请求头信息
                return {
                    code: 200,
                    message: "操作成功",
                    data: _fileter_list
                }     
                // 使用return返回前端需要的数据
            }
        }
        ... // 多个接口
    ]
  5. 现在数据有了,我们如何让mockjs能够拦截到我们前端发出的请求,并能准确区分对应接口呢?

    在mock/文件夹下再建一个index.js写我们mock的监听逻辑
    import Mock from 'mockjs' // 导入mockjs
    
    import demoApi from './demo' // 导入我们模拟数据的js文件
    
    const mocks = [
    {
        intercept: true, // 你可能需要一个开关,来使模拟请求与真实请求并存
        fetchs: demoApi
    },
    
    // 抄来一个解析地址栏参数解析函数
    export function param2Obj(url) {
        const search = url.split('?')[1]
        if (!search) {
            return {}
        }
        return JSON.parse(
            '{"' +
                decodeURIComponent(search)
                .replace(/"/g, '\\"')
                .replace(/&/g, '","')
                .replace(/=/g, '":"')
                .replace(/\+/g, ' ') +
              '"}'
            ) 
        }
        
     // 关键!抄来一个前端模式构建函数(或者你也可以建一个mock server)
     export function mockXHR() {
          Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
          Mock.XHR.prototype.send = function() {
            if (this.custom.xhr) {
              this.custom.xhr.withCredentials = this.withCredentials || false
    
              if (this.responseType) {
                this.custom.xhr.responseType = this.responseType
              }
            }
            this.proxy_send(...arguments)
          }
    
          function XHR2ExpressReqWrap(respond) {
            return function(options) {
              let result = null
              if (respond instanceof Function) {
                const { body, type, url } = options
                // https://expressjs.com/en/4x/api.html#req
                result = respond({
                  method: type,
                  body: JSON.parse(body),
                  query: param2Obj(url)
                })
              } else {
                result = respond
              }
              return Mock.mock(result)
            }
          }
    
          for (const i of mocks) {
            if(i.intercept){
              for(const fetch of i.fetchs){
                Mock.mock(new RegExp(fetch.url), fetch.type || 'get', XHR2ExpressReqWrap(fetch.response))
              }
            }
          }
    }

    经过上面一通代码之后,我们已经初步完成了前端模拟数据的技术条件。

  6. 但是你千万不要忘记了,在main.js引入并使用它哦

     import { mockXHR } from '../mock'
     if(process.env.NODE_ENV == 'development'){
        mockXHR();
     }
  7. 万事具备,只欠请求,你现在就可以写上一个请求试试水了。

    axios.post('/Api/Project/GetList').then(res => {
        console.log(res)
    })

    但是,项目中我们并不建议你直接就这么做!

  8. 再忍一忍,咱们来稍微把请求封装一下。

    src下建一个api文件夹
    然后再在src/api/下建一个_request.js
    import Axios from "axios";
    
        // 定义axios配置 
        const http = Axios.create({
          baseURL: '', // api的base_url
          withCredentials: true, // 开启跨域身份凭证
          method: "post",
          headers: {
            "Content-Type": "application/json;charset=UTF-8"
          },
          timeout: 5000 // request timeout
        });
    
        // 设置全局的请求次数,请求的间隙,用于自动再次请求
        http.defaults.retry = 2;
        http.defaults.retryDelay = 1000;
    
        // 请求拦截器
        http.interceptors.request.use(
          function (config) {
            return config;
          },
          function (error) {
            return Promise.reject(error);
          }
        );
    
        // 响应拦截器
        http.interceptors.response.use(
          function (res) {
            return res;
          },
          function (err) {
            let config = err.config;
            let errres = err.response;
            let err_type = errres ? errres.status : 0;
            // 收集错误信息
            switch (err_type) {
              case 400:
                err.message = "请求无效";
                break;
    
              case 401:
                err.message = "由于长时间未操作,登录已超时,请重新登录";
                break;
    
              case 403:
                err.message = "拒绝访问";
                break;
    
              case 404:
                err.message = `请求地址出错: ${errres.config.url}`;
                break;
    
              case 405:
                err.message = `未授权`;
                break;
    
              case 408:
                err.message = "请求超时";
                break;
    
              case 500:
                err.message = "服务器内部错误";
                break;
    
              case 501:
                err.message = "服务未实现";
                break;
    
              case 502:
                err.message = "网关错误";
                break;
    
              case 503:
                err.message = "服务不可用";
                break;
    
              case 504:
                err.message = "网关超时";
                break;
    
              case 505:
                err.message = "HTTP版本不受支持";
                break;
    
              default:
                err.message = "网络波动,请重试";
            }
    
            // If config does not exist or the retry option is not set, reject
            if (!config || !config.retry) return Promise.reject(err);
    
            // Set the variable for keeping track of the retry count
            config.__retryCount = config.__retryCount || 0;
    
            // Check if we've maxed out the total number of retries
            if (config.__retryCount >= config.retry) {
              // Reject with the error
              return Promise.reject(err);
            }
    
            // Increase the retry count
            config.__retryCount += 1;
    
            // Create new promise to handle exponential backoff
            let backoff = new Promise(function (resolve) {
              setTimeout(function () {
                resolve();
              }, config.retryDelay || 1);
            });
    
            // Return the promise in which recalls axios to retry the request
            return backoff.then(function () {
              return http(config);
            });
          }
        );
    
        export default http;
  9. 封装了一下axios之后,所有的api接口仍建议统一管理

    我们在api/文件夹下再建一个project.js用来统一管理项目模块的所有接口
    import request from "../_request";
    
    // 1获取部门列表接口
    function getProjectListApi(data) {
      return request({
        url: "/Api/Project/GetList",
        method: 'post',
        data
      });
    }
    
    // 2添加项目接口
    function addProjectApi(data) {
      return request({
        url: "/Api/Project/Add",
        method: 'post',
        data
      });
    }
    
    // 3删除项目接口
    function delProjectApi(data) {
      return request({
        url: "/Api/Project/Add",
        method: 'post',
        data
      });
    }
    
    export {
      getProjectListApi, // 1获取部门列表接口
      addProjectApi, // 2添加项目接口
      delProjectApi, // 3删除项目接口
    }
  10. 接下来就是最后一步,在我们开发的.vue文件中使用啦

    import { getProjectListApi } from "@/api/project.js"; // 导入用户列表接口
    
    export default {
        data(){
            return {
                projectList: []
            }
        },
        created(){
            this.getProjectList()
        },
        methods: {
          getProjectList(){
            getProjectListApi().then(res => {
                console.log(res)
            })
          }
        }
    }

结语

不出意外的话,经过上面的一系列操作,我们的数据已经出来啦,mockjs如何在项目中的应用也已经入门完毕,如果你还想被我摸手,就来我的Github交个朋友吧~
Github & segmentfault & 掘金 & csdn & 语雀


蔚蓝
382 声望54 粉丝

微前端实践者,开源项目作者。