Request 请求封装思路

1,733 阅读6分钟

请求封装前言

  • 通常在小程序项目中,微信已经提供了网络请求API,但是往往这点并不能满足我们,所以有了下面的封装
  • Requset请求往往我们有很多需求,此处简单罗列需求并代码实现
    1. 根据不同版本小程序 develop --> trial --> release 版本使用不同域名
    2. request 拦截器,对当前请求发送前做相应处理
    3. 可控式请求 loading 动画,同时支持扩展自定义 loading 文字提示
    4. 根据后端返回请求响应结果做相应处理(列如 210 未登录,220 登录失效等需要与后端共同定义)
  • 笔者使用的是 Taro 进行请求封装,解释步骤基本在代码中有描述,笔者没有对每个方法拆分详细讲述
  • 笔者是根据之前分享的是Taro模板中的请求封装,拆分讲述思路,可供参考借鉴
  • 前言废话太多下面上代码

开始

  • 首先在项目 pages 平级创建 service 文件夹,然后分别建立3个文件分别是:
    1. interceptors.ts -- 请求响应拦截器。
    import Taro from '@tarojs/taro'
    import { HTTP_STATUS } from './status'
    // 笔者这里引入 mobx 是对 未登录,登陆失效 等做处理
    import cartStroe from '../store/user'
    
    const customInterceptor = (chain:any) => {
    
        const requestParams = chain.requestParams
    
        return chain.proceed(requestParams).then((res:any) => {
            // 清除 loading
            if(requestParams.loading) Taro.hideLoading()
            switch(res.statusCode) {
                case HTTP_STATUS.SUCCESS:
                    const result = res.data
                    if(res.data.code === 200) {
                        // 接口调通且无异常赋予success标识
                        result.success = true
                    } else {
                        // 请求接口错误提示,可通过参数中加入 openErrTips: false 关闭
                        if(requestParams.openErrTips && result.msg) Taro.showToast({ title: result.msg, icon: 'none' })
                        // 登录过期或未登录 需要与后端共同定义
                        if(result.code === 210 || result.code === 220) {
                            // 跳转登陆 清空用户信息等 处理
                            cartStroe.setStatus(false)
                            cartStroe.setUser({})
                            Taro.showToast({ title: (result.code === 210 ? '未登录,请先登陆' : '登录信息失效,请重新登陆' ), icon: 'none' })
                            Taro.navigateTo({ url: '/pages/login/index' })
                            return Promise.reject(result)
                        }
                    }
                    return result
    
                case HTTP_STATUS.CREATED:
                    return Promise.reject('请求成功并且服务器创建了新的资源')
    
                case HTTP_STATUS.ACCEPTED:
                    return Promise.reject('接受请求但没创建资源')
    
                case HTTP_STATUS.CLIENT_ERROR:
                    return Promise.reject('服务器不理解请求的语法')
    
                case HTTP_STATUS.AUTHENTICATE:
                    return Promise.reject('请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应')
    
                case HTTP_STATUS.FORBIDDEN:
                    return Promise.reject('服务器拒绝请求')
    
                case HTTP_STATUS.NOT_FOUND:
                    return Promise.reject('服务器找不到请求的网页')
    
                case HTTP_STATUS.SERVER_ERROR:
                    return Promise.reject('(服务器内部错误) 服务器遇到错误,无法完成请求')
    
                case HTTP_STATUS.BAD_GATEWAY:
                    return Promise.reject('(错误网关) 服务器作为网关或代理,从上游服务器收到无效响应')
    
                case HTTP_STATUS.SERVICE_UNAVAILABLE:
                    return Promise.reject('(服务不可用) 服务器目前无法使用(由于超载或停机维护)。 通常,这只是暂时状态。')
    
                case HTTP_STATUS.GATEWAY_TIMEOUT:
                    return Promise.reject('(网关超时) 服务器作为网关或代理,但是没有及时从上游服务器收到请求')
    
                default:
                    console.log('请开发者检查请求拦截未匹配到错误,返回statusCode :>> ', res.statusCode)
                    break
                
            }
        })
    }
    
    
    // Taro 提供了两个内置拦截器
    // logInterceptor - 用于打印请求的相关信息
    // timeoutInterceptor - 在请求超时时抛出错误。
    const interceptors = [customInterceptor, Taro.interceptors.logInterceptor]
    
    export default interceptors
    
    1. status.ts -- HTTP 通用响应状态码提示(方便开发者寻找请求错误源)。
    export const HTTP_STATUS = {
        // 成功处理了请求,一般情况下都是返回此状态码
        SUCCESS: 200,
        // 请求成功并且服务器创建了新的资源
        CREATED: 201,
        // 接受请求但没创建资源
        ACCEPTED: 202,
        // 服务器不理解请求的语法
        CLIENT_ERROR: 400,
        // 请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应
        AUTHENTICATE: 401,
        // 服务器拒绝请求
        FORBIDDEN: 403,
        // 服务器找不到请求的网页
        NOT_FOUND: 404,
        // (服务器内部错误) 服务器遇到错误,无法完成请求
        SERVER_ERROR: 500,
        // (错误网关) 服务器作为网关或代理,从上游服务器收到无效响应
        BAD_GATEWAY: 502,
        // (服务不可用) 服务器目前无法使用(由于超载或停机维护)。 通常,这只是暂时状态。
        SERVICE_UNAVAILABLE: 503,
        // (网关超时) 服务器作为网关或代理,但是没有及时从上游服务器收到请求
        GATEWAY_TIMEOUT: 504
    }
    
    /**
    * 此处有替补实现方式
    * 使用键值对直接通过状态码取值
    * const HTTP_STATUS = { 
    *  '200': '请求服务器端成功',
    *  '201': ''
    * }
    * if(HTTP_STATUS[res.statusCode])
    *  console.log(HTTP_STATUS[res.statusCode])
    * else 
    *  console.log(`请开发者检查请求拦截未匹配到错误,返回statusCode :>> ${res.statusCode}`)
    */
    
    1. index.ts -- 封装请求导出供调用。
    import Taro from '@tarojs/taro'
    
    import interceptors from './interceptors'
    
    interceptors.forEach(interceptorItem => Taro.addInterceptor(interceptorItem))
    // 模块,命名空间,基础接口声明,此处不作解释 自行了解,结尾提供文档指引
    declare namespace RequestProps {
        interface Method {
            'GET',
            'POST',
            'PUT',
            'DELETE'
        }
        interface Options {
            url: string,
            method: keyof Method, 
            data: any,
            loading?: boolean,
            loadingTitle?: string,
            contentType?: string,
            openErrTips?: boolean
        }
        interface requestParams {
            url: string,
            method: keyof Method,
            data: any,
            header: any,
            loading?: boolean,
            loadingTitle?: string,
            contentType?: string,
            openErrTips?: boolean
        }
    }
    
    /**
    * 获取版本 retrun 对应环境域名
    * develop: '开发版', trial: '体验版', release: '正式版'
    * 支持扩展 - 思路 可通过 process.env.NODE_ENV 判断当前打包是 生产模式或工厂模式 进而判断 适合多环境 dev -> beta -> uat -> pro
    * @returns 域名
    */
    const getVersion = () => {
        // @ts-ignore
        switch (__wxConfig.envVersion)
        {
        case 'develop':
            return 'http://develop.gavinpeng.club'
    
        case 'trial':
            return 'http://trial.gavinpeng.club'
    
        case 'release':
            return 'http://release.gavinpeng.club'
    
        default:
            return 'http://develop.gavinpeng.club'
        }
    }
    
    
    class Request {
        baseOptions(options: RequestProps.Options) {
            let { url, method, data } = options
            // 过滤 扩展属性
            let { loading, loadingTitle, contentType, openErrTips, ...rest } = data
    
            if(loading) Taro.showLoading({ title: loadingTitle || '加载中...', mask: true  })
            const requestParams: RequestProps.requestParams = {
                url: getVersion() + url,
                method,
                data: rest,
                header: {
                    // 支持自定义 contentType
                    'content-type': contentType || 'application/json',
                    // Token
                    'Authorization': Taro.getStorageSync('token')
                    // 此处支持扩展,可通过请求 data 参数中加入 header 对象,在上面过滤 语法糖 ...header 此处就不做过多解释,需要的自行添加了解
                    // ...header
                },
                // 请求是否带 loading, 传递到 请求响应拦截器 清除 loading 
                loading,
                openErrTips
            }
            return Taro.request(requestParams)
        }
        
        get(url:string, data:any) {
            return this.baseOptions({ url, method:'GET', data })
        }
    
        post(url:string, data:any) {
            return this.baseOptions({ url, method:'POST', data })
        }
    }
    
    export default new Request()
    
  • 使用方式(笔者对接口模块区分接口管理)
    1. 在项目 pages 平级创建 api 文件夹(统一管理接口)
    2. api 建立 index.ts (导出接口)
    import * as test from './test'
    
    const Api = {
        // 测试模块
        ...test,
        // xxx 模块 
    }
    // 导出所有接口
    export default Api
    
    1. api 建立测试模块接口列表 test.ts
    /*
    * @Author: Gavin
    * @CreateTime: xxxx
    * @Describe: 测试模块相关接口
    */
    // 引入封装后的请求方法
    import request from '@/service/index'
    
    /**
    * 测试
    * @param params 
    * @returns 
    */
    export const isTest = (url:string, data:any) => {
        return request.post(url, data)
    }
    
    1. page 页面引入接口 import Api from '@/api/index' 并使用接口
    Api.isTest({
        id: 1,
        name: 'Gavin',
        // 扩展参数 - 是否需要 loading 非必传默认:false
        loading: true,
        // 扩展参数 - 是否自定义 loadingTitle 非必传默认:加载中...(注:没有开启 loading 此参数无效)
        loadingTitle: '自定义加载提示',
        // 扩展参数 - 是否自定义 contentType 非必传默认:application/json
        contentType: 'x-www-form-urlencoded',
        // 扩展参数 - 是否需要请求错误提示 openErrTips 非必传默认:false
        openErrTips: true,
        // 扩展参数 - 上面讲到的 自定义 header 需要的在封装中添加
        // header: { }
    }).then((res:any) => {
        // 成功
    }).catch((err:any) => {
        // 失败
    })
    

结尾

  • 讲述过程中如有不对或错地方还请指出,欢迎各路大佬们指教
  • 每一次分享是为了进步,在此过程中,讲述代码思路,亦可以很好的锻炼自身的表达能力
  • 成长是生活的动力,加油吧新生代农民工们! TypeScript 中文文档指引 请求封装陈述源码地址