写在前面
笔者又要开发一个小程序了。众所周知,Taro自带的项目不支持本地化mock,目前Taro官方有提供插件机制来支持mock,有这方面需求的可以点开@tarojs/plugin-mock。
鉴于插件的种种弊端,之前笔者曾经用了装饰器模式来支持本地化mock,最近笔者又在学习函数式编程,发现通过函数编程实现来实现mock反而更优雅和利于维护,于是乎行文记录一番。相比装饰器模式,函数式编程的mock方案会有不少的理解和学习成本,如果对装饰器方案有兴趣的话不妨同时食用通过Decorator让Taro支持本地化mock请求
基于Data.task封装请求
Data.task是一个封装了多个接口的函子,笔者曾经在函数式编程的异步请求进行过源码解析。
const http = (method: 'GET' | 'POST', url: string, data: any) => {
return new Task(function (reject, resolve) {
const token = Taro.getStorageSync('token');
Taro.request({
url: `${SERVERURL}${url}`,
method,
data,
dataType: 'json',
header: {
'Content-Type': 'application/json; charset=utf-8',
'token': token,
'Authorization': `Bearer ${token}`
},
success: function (res) {
if (res.statusCode === 200) {
if (res.data.msg === 'SUCCESS') {
resolve(res.data);
} else {
reject(res);
}
} else {
reject(res)
console.log(res)
}
},
fail: function (err) {
console.log(err)
reject(err)
}
});
})
}
组织我们的mock
// 单独管理的api地址 api.ts
export const apisUrl = {
LOGIN: '/api/login',
}
mock数据定义
// mock/login.ts
import { apisUrl } from "@/const/api";
const mock = {
[apisUrl.LOGIN]: {
name: 'lemon',
age: 26,
phone: 15602281234,
level: 1,
token: '123213'
},
}
export default mock;
收集整理所有的mock
// mock/index.ts
import login from './login';
import isFunction from 'lodash/isFunction';
import isObject from 'lodash/isObject';
export const promiseMockData = (res) => ({
data: res,
error_code: 0,
msg: "SUCCESS",
});
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
)
定义mock请求
说白了就是直接url映射
const mock = (method: 'GET' | 'POST', url: string, data: any) => {
return new Task(function (reject, resolve) {
const res = allMockData[url];
console.log('请求路径 -> ', url);
console.log('请求方法 -> ', method);
console.log('请求参数 -> ', data);
console.log('请求结果 -> ', res);
if (res.msg === 'SUCCESS') {
resolve(res.data);
} else {
reject(res);
}
});
}
mock开关
根据条件判断我们是否开启mock
const createHttp = ifMock => ifMock ? mock : http;
自此我们除了Data.task尚未引入任何的函数式编程,接下来开始让curry/compose大展拳脚。
细粒度更高的请求方式
以下的curry和compose来自ramda
const httpApi = createHttp(true); // 获得一个开启mock的请求函数
const curryHttpApi = curry(httpApi); // 柯里化请求函数
// 细分成GET/POST请求
export const Get = curryHttpApi('GET');
export const Post = curryHttpApi('POST');
// 开瓶器/触发Data.task
export const fork = curry(function (f, g, m) {
return m.fork(f, g);
});
// 封装了log的开瓶器
export const action = fork(res => {
console.log('服务器异常,请稍后重试');
console.log('res error -> ', res);
});
使用案例
const login = compose(action(console.log), Get(apisUrl.LOGIN));
useEffect(() => login('lemon'), []);
来对比下async/await
代码清真的不行~
对比装饰器
相比装饰器模式,函数式的mock没有那么大的代码侵入。mock和http真实请求完全隔离 彼此不相干。
同时也省去了大量的重复代码,在装饰器模式下,当我们开关mock时都不得去注释和反注释我们的装饰器,但是在函数式下面清真了许多,我们只需要更改一个参数即可,也可以让他随着环境变量而变化
const httpApi = createHttp(process.env.NODE_ENV === 'development');
总结
函数式编程的魅力不仅于此,当我处于面向类编程的时候,装饰器曾使我沾沾自喜,当我学习函数式编程的时候,发现却能更轻量并且更少的代码的去做到面向类能做到的事情。
函数的力量,总是超乎你想象~