前端本地Mock

652 阅读6分钟

0.背景 为什么需要本地mock?

最终目标:提高联调效率。
1.前端在开发完成之后,如果后端接口还没有打通,那么前端需要在开发阶段仿造一批数据,这些数据一般都是直接写在组件中,然后等到联调的时候再把这些数据删除。这种增加删除的操作个人觉得还是很麻烦的。
2.此外,还有一种情况,当我们在某个开发阶段突然发现后端的测试服务器挂了,此时我们之前写在组件里的mock数据都早已删除,这个时候我们各个接口都是不通的,我们也不可能一一再去mock之前的接口数据,这个时候只能等待后端接口返回。(例如:列表中的点击按钮弹窗,后端接口list数据挂了,我们只有先去mock一下list数据,才能进入弹窗组件开发,这一系列mock操作之后10min过去了,然后开发一段时间,后端接口好了,我们再去删除。)哪天后端接口又挂了,这个时候前端只能在走一遍之前的mock数据,然后删除。个人觉得,针对这种场景,前端mock就是解决这种繁琐的重复操作的。

前置知识:

import fs from 'fs'; // 1.fs文件模块
import url from 'url'; // 2.url模块
import path from 'path'; // 3.path模块

1.mock方案

1.1 直接使用mockJS

1.2 dev服务器的中间件 - onBeforeSetupMiddleware

通过直接使用Mcokjs的缺点就在于,MockJs的原理是在前端XHR对象上进行拦截,没有真正的发出ajax请求。这就导致我们无法通过network控制台去检查返回的数据,而是每次通过console去查看mock数据值,然后再删除console。而通过本地dev服务器可以实现真正的mock,可以在控制台中看见返回的mock数据。

前提条件:使用dev-server首先保证前端发出的请求是打在dev-server上的,而不是直接通过axios发送到后端服务器上的。因为只有前端请求打在dev-server上,这样才能走我们在dev-server服务器上的中间件逻辑,否则这些逻辑就不会触发。
这就要求我们本地采用的跨域方案需要是通过server代理,而不是通过CORS(access-control-allow- origin)来实现。

1.2.1 实现目标

目标1: 响应返回可以显示在network中
目标2: 模块化管理Mock数据前端发出请求返回的Mock数据是在本地文件中读取的
目标3: 向前兼容:对于之前没有采用Mcok的接口是直接返回后端服务器中的数据,采用Mock的接口返回本地Mock数据
目标4: 启动Mock: mock使用时间是在前端开发完成等待联调的时候,如果联调阶段我们希望统一的打到后端服务器的接口上,那么我们就希望关闭本地的Mock接口,这个时候就需要一个环境变量来区分是否开启mock。通过script增加执行命令
需要我们新增一个环境变量,然后在dev服务器执行的时候先判断是否开启Mock,如果环境变量为是,那么就走mock逻辑,否则直接next(),打到后端服务器上。 如何在script字段上新增一个变量

1.3 websokcet实现mock

1.webpack-dev-serve

webpack中的dev-serve本质上是基于express加上webpack-dev-middleware(webpack-dev中间件)实现的。

// webpack-dev-server启动的基本过程伪代码如下
const express = require('express');
const webpackDevMiddleware= require('webpack-dev-middleware');
const app = express();
const devServer = app.use( webpackDevMiddleware(config) )

先加载express,然后加载wepback-dev-middleware中间件。这个webapack-dev中间件主要作用就是以监听模式启动webpack,将编译后的文件放入到内存中,然后与热更新模块API进行沟通便于本地更新。

参考文献:blog.csdn.net/w55100/arti…

需掌握知识:
(1) webpack-dev-server运行原理:cloud.tencent.com/developer/a…
(2) devServer配置项

解析路径

if (isLocalMock) {
  // TODO:
  devServer.before = function (app) {
    function getLocalPath(url, method) {
      let _path = path.resolve(__dirname, `mock${url}`);

      if (method === 'GET'
        && fs.existsSync(_path + '.js')
        && fs.lstatSync(_path + '.js').isFile()) {
        // nothing to do
      } else {
        switch (method) {
          case 'GET':
            _path = path.resolve(_path, 'read');
            break;
          case 'PUT':
            _path = path.resolve(_path, 'update');
            break;
          case 'POST':
            _path = path.resolve(_path, 'create');
            break;
          case 'DELETE':
            _path = path.resolve(_path, 'delete');
            break;
        }
        if (!(fs.existsSync(_path + '.js')
          && fs.lstatSync(_path + '.js').isFile())) {
          return;
        }
      }
      return _path;
    }
    app.use((req, res, next) => {
      const path = getLocalPath(req.path, req.method); // req.method是大写字母
      if (path) {
        let data = '';
        req.on('data', function (chunk) {
          data += chunk;
        });
        req.on('end', function () {
          req.rawBody = data;
          req.jsonBody = data ? JSON.parse(data) : '';
          delete require.cache[require.resolve(path)];
          let mock = require(path);
          if (Object.prototype.toString.call(mock).slice(8, -1) === 'Function') {
            mock = mock({
              params: req.jsonBody,
              query: req.query
            });
          }
          const body = mock.useMockJs ? Mock.mock(mock.body || mock) : (mock.body || mock);
          res.status(mock.status || 200).json(body);
        });
      } else {
        next();
      }
    });
  }
}

path.resolve(): 目标是把一个路径或者路径片段解析为绝对路径为止。给定的路径序列处理的顺序是从右向左处理zhuanlan.zhihu.com/p/262922646 blog.csdn.net/kikyou_csdn…

相关知识点

1.软链接和硬链接

首先,不管是硬链接还是软链接,其共同点都是修改源文件,链接的另一方也生效
软链接:在linux系统中,软连接就和windows中桌面的快捷方式类似,都是通过一个软连接文件指向另一个真正的文件,真正文件的修改也会同样导致软连接文件打开后的结果(ln -s 文件名 链接文件名
硬链接:硬链接不支持指向目录。也就是说“文件A是软链接指向真正的文件B,那么文件B的数据保存在磁盘中,那么文件B就是硬链接”,这个说法是错误的。文件B通过硬链接创建文件C,这个时候文件B和文件C都是指向同一个inode。如果文件B进行修改,那么对应的inode存储的内容就会修改,则文件c打开后就是修改后的数据。另一方面,文件B删除了那么打开文件c还是有之前的数据,因为inode还存在一个硬链接c,所以依然保留数据。因为硬链接不允许目录,即不支持文件夹(ln 文件 文件名)。

2.运行npm run命令发生了什么

bbs.huaweicloud.com/blogs/35266… 查找.bin目录,找到对应同命名的文件,这些文件是对应node-modules下面下载依赖包的软链接, 然后查找对应的js执行文件 image.png 这个时候,执行formula就是脚手架定义的命令,即执行bin/formula.js

3.脚手架构建中的commander

3.1 commander模块:是nodeJs中的一个模块,需要在node环境下运行 command函数接收三个参数:

  1. 命令名称必须:命令后面可跟用<>[]包含的参数;命令的最后一个参数可以是可变的,像实例中那样在数组后面加入...标志;在命令后面传入的参数会被传入到action的回调函数以及program.args数组中。
  2. 命令描述可省略:如果存在,且没有显示调用action(fn),就会启动子命令程序,否则会报错
  3. 配置选项可省略:可配置noHelp、isDefault
#!/usr/bin/env node
const program = require('commander');
const create = require('../src/create');
console.log('123123')
// 命令:pro-cli create app -f,其中pro-cli全局命令是在bin字段定义的,create是二级命令通过command这个API定义,app是二级命令的参数,-f是option定义的选项。当然也可以不执行参数直接pro-cli create  -f
program
    .command('create <projectName>') // 这里输入对应的指令参数‘create’,后面的<name>参数标志,例如vu create app,中的app就是projectName,这个会在action中的回调函数参数中获取到。
    .alias('c') // 配置参数的简写,pro-cli create 可以简写为“pro-cli c”
    .description('create a new project')
    .option('-f, --force', 'message') // 此时执行的命令全称为:pro-cli create app -f,action通过name拿到create app,options用来拿到后面的-f
    .action(name => { // 这里的name就是获取指令参数后面的 参数变量
        console.log("project name is " + name)
    })
program.parse(process.argv) 
// 注意:如果测试阶段,需要在执行aciton命令后,在控制台打印相关信息,一定要在pargram.parse函数中增加参数process.argv,否则会报错。(文献:https://juejin.cn/post/6959750919491682318#heading-2)

commander.command()方法就是用来设置二级命令
option方法定义commander的选项