挑战21天手写前端框架 day16 纯前端的模拟数据

简介: 挑战21天手写前端框架 day16 纯前端的模拟数据

image.png

Mock 数据是现在前后端开发分离很重要的一个环节,可以保证前端的开发进度和后端的开发进度同步开展,而不是前端开发需要在后端服务完成之后才能进行,比如我们可以预先和服务端约定好接口的请求方式和出入参。


比直接在页面中写死数据最大的好处是前端可以提前完成包括请求逻辑这一份的调通工作,到能与服务端连调时,只需要修改请求前缀就可以完成前后端连调工作,当然在实际的开发交付中,很少能够这么顺利的完成的,但是编写本地的 Mock 服务,确实是可以大大的加快整个项目的交付效率。



原理

原理其实和代理是相通的,只不过代理是匹配到特定的前缀,然后转发到目标服务器上,而 Mock 服务是匹配到完整的路径之后,返回一个本地的数据。


当然为了更加方便的进行 Mock 数据的开发,我们约定在项目根目录下面的所有文件,将会被识别成 Mock 文件,即一个文件就是一个 Mock 对象。

export default {
  'GET /mock/hello': {
    text: 'Malita',
  },
}
复制代码

写法是沿用了 umi 中的 Mock 服务的写法,key 值由请求方式+空格+请求路径组合而成。umi 中做了一些友好化处理,为了更聚焦功能的实现,我们这里没有实现这一部分,所以要求 Mock 数据的编写严格按照规定。


为了和前几篇文章中的知识点产出关联,实现上我们也是沿用了day13-用户配置day15-proxy的部分实现方式,如果前几天还不是太懂的朋友,看过今天的内容能够加深理解。



实现

增加配置

你可以和用户配置对比着理解,将 Mock 服务视为是一部分的用户配置文件,只不过,用户配置是我们约定好的某个文件,而 Mock 配置则是我们约定好的整个目录下的所有文件。


新建 examples/app/mock/app.ts 文件名可以任意,其实我们都用不到。你可以将所有的服务写在同一个文件中,但为了便于管理和区分前缀,我建议你在真实的项目中按不同的模块来划分文件内容。


简单的编写两个 Mock 服务。一个 Get 请求,返回一个 Object;一个 POST 请求,返回 Function。

import { Request, Response } from 'express';
export default {
  'GET /mock/hello': {
    text: 'Malita',
  },
  'POST /mock/list': (req: Request, res: Response) => {
    const dataSource = [
      {
        id: 1,
        title: 'Title 1',
      },
      {
        id: 2,
        title: 'Title 2',
      },
      {
        id: 3,
        title: 'Title 3',
      },
      {
        id: 4,
        title: 'Title 4',
      },
      {
        id: 5,
        title: 'Title 5',
      },
    ];
    const { body } = req;
    const { pageSize, offset } = body;
    return res.json({
      total: dataSource.length,
      data: dataSource.slice(offset, offset + pageSize),
    });
  },
};
复制代码


编写 Mock 中间件

新建 packages/malita/src/mock.ts,和我们之前的写法一样,返回一个 Promise。

export const getMockConfig = ({ appData, malitaServe }: { appData: AppData; malitaServe: Server; }) => {
    return new Promise(async (resolve: (value: any) => void, rejects) => {
        const config = {};
        resolve(config);
    })
}
复制代码

因为我们需要需要先获取 mock 目录下的所有的文件,因此这里我们需要安装 glob 模块。


安装 glob 模块

cd packages/malita
pnpm i glob
pnpm i @types/glob -D
复制代码


glob 模块的用法非常简单,你只要指定查找的路径,然后匹配你需要的文件正则即可。比如我们这里找出 mock 文件夹下的所有 ts 文件。

const mockDir = path.resolve(appData.paths.cwd, 'mock');
const mockFiles = glob.sync('**/*.ts', {
  cwd: mockDir,
});
// mockFiles [app.ts]
复制代码


获取所有的 mock 文件

因为它找到的文件是相对于我们提供的 cwd 的,所以我们需要进一步的取到文件的绝对路径。

const ret = mockFiles.map((memo) => path.join(mockDir, memo));
复制代码


将 mock 文件编译成 js 文件

然后参照用户配置那边的写法,先将目标文件编译成 js 文件,写到我们的临时文件中。

const mockOutDir = path.resolve(appData.paths.absTmpPath, 'mock');
await build({
  format: 'cjs',
  logLevel: 'error',
  outdir: mockOutDir,
  bundle: true,
  watch: {
      onRebuild: (err, res) => {
          if (err) {
              console.error(JSON.stringify(err));
              return;
          }
          malitaServe.emit('REBUILD', { appData });
      }
  },
  define: {
      'process.env.NODE_ENV': JSON.stringify('development'),
  },
  external: ['esbuild'],
  entryPoints: ret,
});
复制代码


读取 mock 配置

然后读取编译后的文件,获得我们需要的数据。

try {
    const outMockFiles = glob.sync('**/*.js', {
        cwd: mockOutDir,
    });
    cleanRequireCache(mockOutDir);
    config = outMockFiles.reduce((memo, mockFile) => {
        memo = {
            ...memo,
            ...require(path.resolve(mockOutDir, mockFile)).default,
        };
        return memo;
    }, {});
} catch (error) {
    console.error('getMockConfig error', error);
    rejects(error);
}
resolve(config);
复制代码


系列化 mock 数据

因为我们约定了 key 值由请求方式+空格+请求路径组合而成,因此我们需要先处理数据,将请求方式和请求路径提取出来。

- resolve(config);
+ resolve(normalizeConfig(config));
function normalizeConfig(config: any) {
    return Object.keys(config).reduce((memo: any, key) => {
        const handler = config[key];
        const type = typeof handler;
        // 如果不符合规范,我们舍弃它
        if (type !== 'function' && type !== 'object') {
            return memo;
        }
        const req = key.split(' ');
        const method = req[0];
        const url = req[1];
        if (!memo[method]) memo[method] = {};
        memo[method][url] = handler;
        return memo;
    }, {});
}
复制代码


操作之后我们会得到一个对象 config

{
  GET: { '/mock/hello': { text: 'Malita' } },
  POST: { '/mock/list': [Function: POST /mock/list] }
}
复制代码


使用 mock 中间件

接下里的使用就很简单了,参照 proxy 中中间件的添加方式。

packages/malita/src/dev.ts 中:

import { getMockConfig } from './mock';
const buildMain = async ({ appData }: { appData: AppData }) => {
    // getUserConfig
    // 获取 mock 数据
    const mockConfig = await getMockConfig({
        appData, malitaServe
    });
    app.use((req, res, next) => {
        const result = mockConfig?.[req.method]?.[req.url];
        if (Object.prototype.toString.call(result) === "[object String]" || Object.prototype.toString.call(result) === "[object Array]" || Object.prototype.toString.call(result) === "[object Object]") {
            res.json(result)
        } else if (Object.prototype.toString.call(result) === "[object Function]") {
            result(req, res);
        } else {
            next();
        }
    });
    // createProxyMiddleware
}
复制代码


运行验证

cd examples/app
pnpm dev
> malita dev
App listening at http://127.0.0.1:8888
复制代码

浏览器中访问 http://127.0.0.1:8888/mock/hello

{ "text": "Malita" }
复制代码


其他

为了编写 Mock 数据编写更加的高效和便捷,可以在项目中引入一些第三方的 Mock 数据生成库,比如 mock.js

import mockjs from 'mockjs';
export default {
  // 使用 mockjs 等三方库
  'GET /mock/tags': mockjs.mock({
    'list|100': [{ name: '@city', 'value|1-100': 50, 'type|0-2': 1 }],
  }),
};
复制代码


这样就会随机生成 100 条数据,比我们上面写的 Title 1-5 高明的多了。


感谢阅读,今天的内容就到这里了。距离这个系列结束,仅剩 5 天了,如果你喜欢我的叙述方式,还有其他想看的的系列或者文章,可以通过评论或者微信联系我。我期望将每一个知识点都讲的明白一些,能够帮助到你,哪怕成果仅仅是“入门级别”,我也很满足了。


源码归档

目录
相关文章
|
1月前
|
JSON 前端开发 Java
震惊!图文并茂——Java后端如何响应不同格式的数据给前端(带源码)
文章介绍了Java后端如何使用Spring Boot框架响应不同格式的数据给前端,包括返回静态页面、数据、HTML代码片段、JSON对象、设置状态码和响应的Header。
132 1
震惊!图文并茂——Java后端如何响应不同格式的数据给前端(带源码)
|
23天前
|
监控 JavaScript 前端开发
前端的混合之路Meteor篇(六):发布订阅示例代码及如何将Meteor的响应数据映射到vue3的reactive系统
本文介绍了 Meteor 3.0 中的发布-订阅模型,详细讲解了如何在服务器端通过 `Meteor.publish` 发布数据,包括简单发布和自定义发布。客户端则通过 `Meteor.subscribe` 订阅数据,并使用 MiniMongo 实现实时数据同步。此外,还展示了如何在 Vue 3 中将 MiniMongo 的 `cursor` 转化为响应式数组,实现数据的自动更新。
|
23天前
|
JSON 分布式计算 前端开发
前端的全栈之路Meteor篇(七):轻量的NoSql分布式数据协议同步协议DDP深度剖析
本文深入探讨了DDP(Distributed Data Protocol)协议,这是一种在Meteor框架中广泛使用的发布/订阅协议,支持实时数据同步。文章详细介绍了DDP的主要特点、消息类型、协议流程及其在Meteor中的应用,包括实时数据同步、用户界面响应、分布式计算、多客户端协作和离线支持等。通过学习DDP,开发者可以构建响应迅速、适应性强的现代Web应用。
|
1月前
|
JavaScript 前端开发 Python
django接收前端vue传输的formData图片数据
django接收前端vue传输的formData图片数据
33 4
|
1月前
|
JavaScript 前端开发 网络架构
|
23天前
|
NoSQL 前端开发 MongoDB
前端的全栈之路Meteor篇(三):运行在浏览器端的NoSQL数据库副本-MiniMongo介绍及其前后端数据实时同步示例
MiniMongo 是 Meteor 框架中的客户端数据库组件,模拟了 MongoDB 的核心功能,允许前端开发者使用类似 MongoDB 的 API 进行数据操作。通过 Meteor 的数据同步机制,MiniMongo 与服务器端的 MongoDB 实现实时数据同步,确保数据一致性,支持发布/订阅模型和响应式数据源,适用于实时聊天、项目管理和协作工具等应用场景。
|
30天前
|
存储 前端开发 API
前端开发中,Web Storage的存储数据的方法localstorage和sessionStorage的使用及区别
前端开发中,Web Storage的存储数据的方法localstorage和sessionStorage的使用及区别
90 0
|
1月前
|
前端开发 Java 数据库
springBoot:template engine&自定义一个mvc&后端给前端传数据&增删改查 (三)
本文介绍了如何自定义一个 MVC 框架,包括后端向前端传递数据、前后端代理配置、实现增删改查功能以及分页查询。详细展示了代码示例,从配置文件到控制器、服务层和数据访问层的实现,帮助开发者快速理解和应用。
|
3月前
|
前端开发 JavaScript
这篇文章介绍了如何使用form表单结合Bootstrap格式将前端数据通过action属性提交到后端的servlet,包括前端表单的创建、数据的一级和二级验证,以及后端servlet的注解和参数获取。
这篇文章介绍了使用AJAX技术将前端页面中表单接收的多个参数快速便捷地传输到后端servlet的方法,并通过示例代码展示了前端JavaScript中的AJAX调用和后端servlet的接收处理。
这篇文章介绍了如何使用form表单结合Bootstrap格式将前端数据通过action属性提交到后端的servlet,包括前端表单的创建、数据的一级和二级验证,以及后端servlet的注解和参数获取。
|
3月前
|
前端开发
第一种方式:使用form表单将前端数据提交到servelt(将前端数据提交到servlet)
这篇文章介绍了如何使用form表单结合Bootstrap格式将前端数据通过action属性提交到后端的servlet,包括前端表单的创建、数据的一级和二级验证,以及后端servlet的注解和参数获取。
第一种方式:使用form表单将前端数据提交到servelt(将前端数据提交到servlet)