函数式编程的mock方案

534 阅读3分钟

写在前面

笔者又要开发一个小程序了。众所周知,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'), []);

image.png 来对比下async/await

image.png

代码清真的不行~

对比装饰器

相比装饰器模式,函数式的mock没有那么大的代码侵入。mock和http真实请求完全隔离 彼此不相干。

image.png

同时也省去了大量的重复代码,在装饰器模式下,当我们开关mock时都不得去注释和反注释我们的装饰器,但是在函数式下面清真了许多,我们只需要更改一个参数即可,也可以让他随着环境变量而变化

const httpApi = createHttp(process.env.NODE_ENV === 'development'); 

总结

函数式编程的魅力不仅于此,当我处于面向类编程的时候,装饰器曾使我沾沾自喜,当我学习函数式编程的时候,发现却能更轻量并且更少的代码的去做到面向类能做到的事情。

函数的力量,总是超乎你想象~