前言
先说一下为什么要干这个事,笔者最近又开始开发小程序了,想起之前团队的小程序项目呢采取mini+server的模式,小程序中的请求经过node server(egg\express\koa)然后在node端去mock返回,某种程度上也增加项目复杂度,开发小程序的同时还要去维护另外的一个项目形成了一种依赖耦合。于是乎开始构思如果实现Taro的本地化mock,在Taro社区和文档查阅的相关资料都只有插件的情况下略感不满,遂有此文。通过Taro官方提供的cli初始化整个小程序项目的时候,taro的模板虽然提供ts/react/less等语法支持,但是对于mock的请求不支持。如果要支持mock,官方也有提供的插件@tarojs/plugin-mock 但是本人碍于强迫症,对于个人所维护的项目秉持着插件越少越好的原则,因为过多的插件不仅会带来很大的维护成本以及接手成本,下一个人接手的时候遇上没遇到过的插件无疑都要一一翻看文档,相比起代码支持Mock来说,代码的阅读性比起黑盒子插件可观的多了。况且mock本身属于业务层的范畴,如果使用了这个插件,配置文件还要引入业务的mock data,个人觉得是一种污染吧。本文部分代码使用了ts,建议可先阅读上篇文章效果更佳拥抱ts之后更优雅的异步请求处理;
初始化Taro
通过taro cli可以初始化一个如下模板项目 项目非常简单 起初只有pages和store模块。如果要让他支持mock 需要加以改造。
封装Taro请求
在src目录下新建一个模块helpers,在新建一个api.ts的文件干这个事,先封装一下Taro.request
function http(method: 'POST' | 'GET', url, data = {}) {
return new Promise(function (resolve, reject) {
Taro.request({
url: `${SERVERURL}${url}`,
method,
data,
dataType: 'json',
header: {
'Content-Type': 'application/json; charset=utf-8',
'token': token
},
success: function (res) {
if (res.statusCode === 200) {
if (res.data.code === 400) {
console.log('400,参数错误')
reject(res)
}
if (res.data.code === 401) {
console.log('401,登录过期')
Taro.showToast({
title: '登录过期,重新登录',
icon: 'none',
duration: 2000
})
reject(res)
}
if (res.data.code === 404) {
console.log('404,接口错误')
reject(res)
}
if (res.data.code === 500) {
console.log('500,后台程序错误')
reject(res)
} else {
resolve(res)
console.log(res)
}
} else {
reject(res)
console.log(res)
}
},
fail: function (err) {
console.log(err)
reject(err)
}
})
})
}
管理mock数据
src目录下新增mock目录,单独管理mock数据,并且可以按照业务需要按照业务模块去划分mock。 intex 负责收集所有的mock,封装成promise进行输出。
// index.ts
import login from './login';
import isFunction from 'lodash/isFunction';
import isObject from 'lodash/isObject';
export const promiseMockData = (res) => {
return Promise.resolve([
{
data: res,
error_code: 0,
message: "ok",
},
undefined
]);
}
function pack(...mocks) {
const allMocks = mocks.reduce((totalMock, mock) => {
let mockAPIs;
if (isFunction(mock)) {
mockAPIs = mock();
} else if (isObject(mock)) {
mockAPIs = mock;
} else {
throw new Error('mock file require both Function or Object');
}
return {
...totalMock,
...mockAPIs
}
});
Object.keys(allMocks).forEach(key => {
// 定制化
if (isFunction(allMocks[key])) {
allMocks[key] = allMocks[key]();
} else {
allMocks[key] = promiseMockData(allMocks[key])
}
});
return allMocks;
}
export const allMockData = pack(
login
)
login就是普通的业务了,可以按照api对mock进行书写,非常简单易用,因为内部封装了status等信息,对外部mock的部分只暴露了data部分,如果需要定制化返回的api对应的也可以采用函数
const mock = {
'/api/minilogin': {
name: 'lemon',
age: 26,
phone: 15602281234,
level: 1,
token: '123213'
}
}
export default mock;
拦截请求进行mock映射
回到我们的helpers/api.ts的模块,其实要拦截一个请求可以把这个问题看成拦截一个函数一个方法,实现的方式有很多种,笔者能想到的办法有如下几种:
- 设置钩子,仿照axios-mock-adaptor的设计,这个工具将axios传入,并且在请求的时候通过拦截onGet/onPost钩子,把mock数据返回。
- 中间件,可以仿照redux的dispatch的chunk-middleware设计,将Mock数据返回前置于请求
- Decorator装饰器模式,拦截方法的结果,用Mock数据替代并返回。
三种模式都有各自的好处,本文采用的是装饰器模式,话不多说可以先看看这个装饰器做了啥
import { allMockData } from '../mock';
function mockDecorator(target, key, descriptor) {
const oldValue = descriptor.value;
// 修改 descriptor属性 注入mock
descriptor.value = function (p: { url: string, data?: any, form?: boolean }) {
// 判断环境变量 返回mock
if (process.env.NODE_ENV === 'development') {
return allMockData[p.url];
} else {
return oldValue.apply(this, arguments);
}
}
return descriptor;
}
import了mock/index输出得mock data集合,判断环境环境更改原来方法的输出,但是因为装饰器只能用于class类,所以我们需要对我们之前设计的http的方法进行改造。
// 返回一个结果固定的promise
function packPromise<T = any>(p: Promise<any>): [T, any] {
return p.then(res => [res, undefined]).catch(err => [undefined, err]) as unknown as [T, any];
}
class Api {
@mockDecorator
async Post<T = any>(p: { url: string, data?: any, form?: boolean }): Promise<[FetchRes<T>, any]> {
const { url, data = {}, form = false } = p;
const [res, err] = await packPromise<FetchRes<T>>(http('POST', url, form ? assign(data, { form }) : data));
return [res, err]
}
@mockDecorator
async Get<T = any>(p: { url: string, params?: any }): Promise<[FetchRes<T>, any]> {
const { url, params = {} } = p;
const [res, err] = await packPromise<FetchRes<T>>(http('GET', url, params));
return [res, err]
}
}
const api = new Api();
export const { Get, Post } = api;
检验mock效果。
在pages页面写下如下业务代码
// @ts-nocheck
import React, { Component } from 'react'
import { View } from '@tarojs/components'
import './index.less'
export interface UserData {
name: string
code: string
date: number
avator: string
level: number
token: string
}
class Index extends Component {
componentDidMount() {
this.login()
}
login = async () => {
const [res, err] = await Get<UserData>({
url: '/api/minilogin'
});
switch (true) {
case !!err:
console.log('login err');
break;
case !!res:
console.log('login success');
console.log(res);
break;
default:
console.log('login not res');
}
}
render () {
return (
<View className='index'>
login
</View>
)
}
}
export default Index
控制台输出如下
总结
一切do work之后,本地化mock实在太舒适了,不需要依赖其他的项目,等到后台可以联调的时候更改全局变量SERVERURL和环境变量即可去掉mock。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!