最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 通过Decorator让Taro支持本地化mock请求

    正文概述 掘金(lemonwater)   2021-01-17   798

    前言

    先说一下为什么要干这个事,笔者最近又开始开发小程序了,想起之前团队的小程序项目呢采取mini+server的模式,小程序中的请求经过node server(egg\express\koa)然后在node端去mock返回,某种程度上也增加项目复杂度,开发小程序的同时还要去维护另外的一个项目形成了一种依赖耦合。于是乎开始构思如果实现Taro的本地化mock,在Taro社区和文档查阅的相关资料都只有插件的情况下略感不满,遂有此文。通过Taro官方提供的cli初始化整个小程序项目的时候,taro的模板虽然提供ts/react/less等语法支持,但是对于mock的请求不支持。如果要支持mock,官方也有提供的插件@tarojs/plugin-mock 通过Decorator让Taro支持本地化mock请求 但是本人碍于强迫症,对于个人所维护的项目秉持着插件越少越好的原则,因为过多的插件不仅会带来很大的维护成本以及接手成本,下一个人接手的时候遇上没遇到过的插件无疑都要一一翻看文档,相比起代码支持Mock来说,代码的阅读性比起黑盒子插件可观的多了。况且mock本身属于业务层的范畴,如果使用了这个插件,配置文件还要引入业务的mock data,个人觉得是一种污染吧。本文部分代码使用了ts,建议可先阅读上篇文章效果更佳拥抱ts之后更优雅的异步请求处理;

    初始化Taro

    通过taro cli可以初始化一个如下模板项目 通过Decorator让Taro支持本地化mock请求 项目非常简单 起初只有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。 通过Decorator让Taro支持本地化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
    
    

    控制台输出如下 通过Decorator让Taro支持本地化mock请求

    总结

    一切do work之后,本地化mock实在太舒适了,不需要依赖其他的项目,等到后台可以联调的时候更改全局变量SERVERURL和环境变量即可去掉mock。


    起源地下载网 » 通过Decorator让Taro支持本地化mock请求

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元