挑战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 天了,如果你喜欢我的叙述方式,还有其他想看的的系列或者文章,可以通过评论或者微信联系我。我期望将每一个知识点都讲的明白一些,能够帮助到你,哪怕成果仅仅是“入门级别”,我也很满足了。


源码归档

目录
相关文章
|
6月前
|
前端开发
【前端统计图】hcharts实现堆叠柱形图(与后台数据交互)
【前端统计图】hcharts实现堆叠柱形图(与后台数据交互)
34 0
|
2天前
|
前端开发 JavaScript API
前端 excelex 包可将数据保存为 xls、xlsx、csv、txt 文件(支持单元格样式、合并单元格等)
前端 excelex 包可将数据保存为 xls、xlsx、csv、txt 文件(支持单元格样式、合并单元格等)
65 1
|
2天前
|
前端开发 JavaScript 关系型数据库
若依框架------后台路由数据是如何转换为前端路由信息的
若依框架------后台路由数据是如何转换为前端路由信息的
131 0
|
2天前
|
前端开发 关系型数据库 MySQL
SpringBoot-----从前端更新数据到MySql数据库
SpringBoot-----从前端更新数据到MySql数据库
9 1
|
2天前
|
JSON JavaScript Java
从前端Vue到后端Spring Boot:接收JSON数据的正确姿势
从前端Vue到后端Spring Boot:接收JSON数据的正确姿势
25 0
|
2天前
|
前端开发 JavaScript 开发者
【专栏:HTML与CSS前端技术趋势篇】前端框架(React/Vue/Angular)与HTML/CSS的结合使用
【4月更文挑战第30天】前端框架React、Vue和Angular助力UI开发,通过组件化、状态管理和虚拟DOM提升效率。这些框架与HTML/CSS结合,使用模板语法、样式管理及组件化思想。未来趋势包括框架简化、Web组件标准采用和CSS在框架中角色的演变。开发者需紧跟技术发展,掌握新工具,提升开发效能。
|
2天前
|
存储 缓存 前端开发
前端如何利用indexDB进行数据优化
使用IndexedDB作为浏览器内置的客户端数据库,用于存储大量数据和实现离线支持。它能缓存常用数据,减少服务器请求,提高用户体验。IndexedDB支持数据索引、复杂查询及版本管理,允许离线操作并同步到服务器。但需熟悉其异步API,可借助Dexie.js、localForage等库简化使用。
|
2天前
|
前端开发 API 数据库
Django(五):如何在Django中通过API提供数据库数据给前端
Django(五):如何在Django中通过API提供数据库数据给前端
|
2天前
|
前端开发 JavaScript
前端年度需求最大的前端框架都有那些?
【4月更文挑战第7天】 前端框架如React、Angular、Vue.js和Svelte各有优势,选择需考虑项目需求、团队经验、社区支持、性能和学习曲线。React适合高性能UI,Angular适合大型企业应用,Vue.js轻量且易学,Svelte则以高性能著称。活跃社区、丰富的第三方库和良好文档是重要考量因素。
20 0
|
2天前
|
前端开发 Java API
WebSocket vs SSE: 实时数据推送到前端的选择与实现(详细)
WebSocket vs SSE: 实时数据推送到前端的选择与实现(详细)
357 0