我正在参与创作者训练营第5期, 点击了解活动概况

最近考摩托驾照耽误了一些时间,我们见谅。
git地址: github.com/Sincenir/si…
觉得对我们有帮忙的,望留下 点赞收藏star ~~~

前语(为何做)

以前的一段时间,我都认为 接口央求 封装是前端的必修课。 只要是写过出产环境前端代码的人,应该都脱离不了异步接口央求,那么 接口央求 的 封装 是必经之路。

直到前些天,我们屋某个美团写后台的小姑娘问我前端问题时。我才发现她们代码中的 接口央求 ,都是没有任何的封装,直接选用以下方法进行:

axios.post(`/api/xxxx/xxxx?xxx=${xxx}`, { ...data })
.then((result) => {
    if (result.errno) {
        ....
    } else {
        ....
    }
})
.catch((err) => {
    ....
})

这样写也不是说不好,在某种程度上,这增加了代码的可读性。
但是我们大多数页面需求的接口都不止一个,那么我们的组件中极有或许出现 数十上百 行重复代码。

那么跟着央求的体量越来越大,我们的项目便越来越难以保护。

效果演示

const [e, r] = await api.getUserInfo(userid)
if (!e && r) this.userInfo = r.data.userinfo

上面是我们毕竟的结束效果。

接下来,我将带我们一步一步封装一套归于我们自己的 接口央求工具 ,同时也希望我们共享更好的思路。

注:

  • 假设你希望直接看源码,请翻到 《完好代码》
  • 这儿以 axios 作演示,同样换成 fetch 、 小程序的 request 都是可以的
  • 我将会选用 typeScript 书写这段教程,假设你不需求,忽略掉对应的类型即可

思路清楚,先说剖析(做什么)

在我们正式开发前,首要需求清楚央求一个接口都做了什么。

为此,消耗了两个小时时间,做了一个央求流程图,以便于我们后续进行需求剖析(小声bb:Processon真难用 )

前端架构带你 封装axios,一次封装终身获益「美团后端连连点赞」

有了一个清楚的央求流程图,我们便可以差异出来两块重要的内容来进行拆分: 基础央求流程阻挠器

接下来我们将两块儿内容展开讲。

基础央求流程

基础央求流程,我们大致可以分为三块, 一是 央求进入央求阻挠前 、二是 真实建议的央求 、三是 央求从照应阻挠出来后

这其间可以归为两类,
一类是 针对单独接口的处理
二类是 针对全部接口需求的内容

  • 针对单独接口的处理
    • 央求前的参数处理
    • 央求后的回来值处理
  • 针对全部接口的处理
    • Post
    • Get
    • Put
    • Del

阻挠器

阻挠器,我们大致可以分为两类, 一类是 央求接口前的共同处理(央求阻挠) 、 一类是 央求接口后的共同处理(照应阻挠)

  • 央求阻挠
    • 央求调整
    • 用户标识
  • 照应阻挠
    • 网络差错处理
    • 授权差错处理
    • 一般差错处理
    • 代码异常处理

共同调用

跟着我们的 Api 越来越多,我们或许需求给他们不同的分类,但我们并不希望每次调用都从不同的文件夹引进不同的 Api ,因而在 基础央求 + 阻挠器 之外,我们还需求一个封包操作。

开发次第

跟着我们要做的内容越来越多,我们希望它有一个次第以便于我们墨守成规的开发(信任我们对开发中出现的不确定性都深恶痛绝)。
以便于我们按照流程,无意外、无惊喜 的结束此次封装。

在我们的开发中,我们底子要遵从先处理通用内容在处理个性化内容的逻辑:

  1. 针对全部接口的处理(Get)
  2. 央求阻挠
  3. 照应阻挠
  4. 针对单独接口的处理
  5. 封包处理
  6. 针对全部接口的处理(Post、Put、Del)

tips

这儿我们或许意外为什么 Post、Put、Del 的处理在终究开发: 因为大多数情况,我们开发中希望所编写的内容有一个及时的回馈。

举个栗子:我在生活中发现 → 我们学习吉他时,大多数人半途而废了。但坚持下来的人底子无一例外的通过吉他在不同的阶段都获得了好处,包含但不限于 异性 的夸奖、舍友的拍手、 get女朋友 。 这也是我们在结业独处后,很难学会弹吉他的原因(无处炫耀)。

因而,我们需求让所开发的内容尽快到达可用的阶段(MVP)。

万事俱备、只欠东风(怎么做)

按照我们之前定好的次第,墨守成规的开发⑧!

针对全部接口的处理(Get)

我们希望以 const [e, r] = await api.getUserInfo(id) 的方法调用,代表着我们需求保证回来值安稳的回来 [err, result] ,所以我们需求在央求不管成功失败时,都以 resolve 方法调用。

同时,我们希望我们可以处理回来值,因而在这儿封装了 clearFn 的回调函数。

type Fn = (data: FcResponse<any>) => unknown
interface IAnyObj {
  [index: string]: unknown
}
interface FcResponse<T> {
  errno: string
  errmsg: string
  data: T
}
const get = <T,>(url: string, params: IAnyObj = {}, clearFn?: Fn): Promise<[any, FcResponse<T> | undefined]> =>
  new Promise((resolve) => {
    axios
      .get(url, { params })
      .then((result) => {
        let res: FcResponse<T>
        if (clearFn !== undefined) {
          res = clearFn(result.data) as unknown as FcResponse<T>
        } else {
          res = result.data as FcResponse<T>
        }
        resolve([null, res as FcResponse<T>])
      })
      .catch((err) => {
        resolve([err, undefined])
      })
  })

央求阻挠

央求阻挠中,我们需求两块内容,一是 央求的调整 ,二是 配备用户标识

const handleRequestHeader = (config) => {
    config['xxxx'] = 'xxx'
    return config
}
const handleAuth = (config) => {
    config.header['token'] = localStorage.getItem('token') || token || ''
    return config
}
axios.interceptors.request.use((config) => {
    config = handleChangeRequestHeader(config)
    config = handleConfigureAuth(config)
    return config
})

照应阻挠

照应差错由三类差错组成:

  • 网络差错处理
  • 授权差错处理
  • 一般差错处理

因而,要高雅的处理照应阻挠,我们有必要先将三类差错函数写好,以便于我们增强代码扩展性及后期保护。

差错处理函数

const handleNetworkError = (errStatus) => {
    let errMessage = '不知道差错'
    if (errStatus) {
        switch (errStatus) {
            case 400:
                errMessage = '差错的央求'
                break
            case 401:
                errMessage = '未授权,请从头登录'
                break
            case 403:
                errMessage = '回绝访问'
                break
            case 404:
                errMessage = '央求差错,未找到该资源'
                break
            case 405:
                errMessage = '央求方法未容许'
                break
            case 408:
                errMessage = '央求超时'
                break
            case 500:
                errMessage = '服务器端犯错'
                break
            case 501:
                errMessage = '网络未结束'
                break
            case 502:
                errMessage = '网络差错'
                break
            case 503:
                errMessage = '服务不可用'
                break
            case 504:
                errMessage = '网络超时'
                break
            case 505:
                errMessage = 'http版别不支持该央求'
                break
            default:
                errMessage = `其他联接差错 --${errStatus}`
        }
    } else {
        errMessage = `无法联接到服务器!`
    }
    message.error(errMessage)
}
const handleAuthError = (errno) => {
	const authErrMap: any = {
	  '10031': '登录失效,需求从头登录', // token 失效
	  '10032': '您太久没登录,请从头登录~', // token 过期
	  '10033': '账户未绑定人物,请联络管理员绑定人物',
	  '10034': '该用户未注册,请联络管理员注册用户',
	  '10035': 'code 无法获取对应第三方渠道用户',
	  '10036': '该账户未相关员工,请联络管理员做相关',
	  '10037': '账号已无效',
	  '10038': '账号未找到',
	}
	if (authErrMap.hasOwnProperty(errno)) {
		message.error(authErrMap[errno])
		// 授权差错,登出账户
		logout()
		return false
	}
	return true
}
const handleGeneralError = (errno, errmsg) => {
	if (err.errno !== '0') {
		meessage.error(err.errmsg)
		return false
	}
	return true
}

适配

当我们将全部的差错类型处理函数写完,在 axios 的阻挠器中进行调用即可。

axios.interceptors.response.use(
    (response) => {
        if (response.status !== 200) return Promise.reject(response.data)
        handleAuthError(response.data.errno)
        handleGeneralError(response.data.errno, response.data.errmsg)
        return response
    },
    (err) => {
        handleNetworkError(err.response.status)
        Promise.reject(err.response)
    }
)

针对单独接口的处理

根据上面的几类通用处理,我们这个央求的封装底子现已可用了。

但是我们还有一些额定的操作无处存放(参数处理、回来值处理),且我们并不想将他们耦合在页面中每次调用进行处理,那么我们明显需求一个位置来处理这些内容。

import { Get } from "../server"
interface FcResponse<T> {
      errno: string
      errmsg: string
      data: T
}
type ApiResponse<T> = Promise<[any, FcResponse<T> | undefined]>
function getUserInfo<T extends { id: string; name: string; }>(id): ApiResponse<T> {
      return Get<T>('/user/info', { userid: id })
}

封包处理

接口分类封包

用户数据: api/path/user.ts

import { Get } from "../server"
export function getUserInfo(id) { ... }
export function getUserName(id) { ... }
export const userApi = {
	getUserInfo,
	getUserName
}

订单数据: api/path/shoporder.ts

import { Get } from "../server"
function getShoporderDetail() { ... }
function getShoporderList() { ... }
export const shoporderApi = {
	getShoporderDetail,
	getShoporderList
}

调用点共同

api/index.ts

import { userApi } from "./path/user"
import { shoporderApi } from "./path/shoporder"
export const api = {
	...userApi,
	...shoporderApi
}

针对全部接口的处理(Post、Put、Del)

export const post = <T,>(url: string, data: IAnyObj, params: IAnyObj = {}): Promise<[any, FcResponse<T> | undefined]> => {
  return new Promise((resolve) => {
    axios
      .post(url, data, { params })
      .then((result) => {
        resolve([null, result.data as FcResponse<T>])
      })
      .catch((err) => {
        resolve([err, undefined])
      })
  })
}
// Put / Del 同理

完好代码

前端架构带你 封装axios,一次封装终身获益「美团后端连连点赞」
事务处理函数: src/api/tool.ts

const handleRequestHeader = (config) => {
	config['xxxx'] = 'xxx'
	return config
}
const handleAuth = (config) => {
	config.header['token'] = localStorage.getItem('token') || token || ''
	return config
}
const handleNetworkError = (errStatus) => {
    let errMessage = '不知道差错'
    if (errStatus) {
        switch (errStatus) {
            case 400:
                errMessage = '差错的央求'
                break
            case 401:
                errMessage = '未授权,请从头登录'
                break
            case 403:
                errMessage = '回绝访问'
                break
            case 404:
                errMessage = '央求差错,未找到该资源'
                break
            case 405:
                errMessage = '央求方法未容许'
                break
            case 408:
                errMessage = '央求超时'
                break
            case 500:
                errMessage = '服务器端犯错'
                break
            case 501:
                errMessage = '网络未结束'
                break
            case 502:
                errMessage = '网络差错'
                break
            case 503:
                errMessage = '服务不可用'
                break
            case 504:
                errMessage = '网络超时'
                break
            case 505:
                errMessage = 'http版别不支持该央求'
                break
            default:
                errMessage = `其他联接差错 --${errStatus}`
        }
    } else {
        errMessage = `无法联接到服务器!`
    }
    message.error(errMessage)
}
const handleAuthError = (errno) => {
	const authErrMap: any = {
	  '10031': '登录失效,需求从头登录', // token 失效
	  '10032': '您太久没登录,请从头登录~', // token 过期
	  '10033': '账户未绑定人物,请联络管理员绑定人物',
	  '10034': '该用户未注册,请联络管理员注册用户',
	  '10035': 'code 无法获取对应第三方渠道用户',
	  '10036': '该账户未相关员工,请联络管理员做相关',
	  '10037': '账号已无效',
	  '10038': '账号未找到',
	}
	if (authErrMap.hasOwnProperty(errno)) {
		message.error(authErrMap[errno])
		// 授权差错,登出账户
		logout()
		return false
	}
	return true
}
const handleGeneralError = (errno, errmsg) => {
	if (err.errno !== '0') {
		meessage.error(err.errmsg)
		return false
	}
	return true
}

通用操作封装: src/api/server.ts

import axios from 'axios'
import { message } from 'antd'
import {
	handleChangeRequestHeader,
	handleConfigureAuth,
	handleAuthError,
	handleGeneralError,
	handleNetworkError
} from './tools'
type Fn = (data: FcResponse<any>) => unknown
interface IAnyObj {
    [index: string]: unknown
}
interface FcResponse<T> {
    errno: string
    errmsg: string
    data: T
}
axios.interceptors.request.use((config) => {
  config = handleChangeRequestHeader(config)
	config = handleConfigureAuth(config)
	return config
})
axios.interceptors.response.use(
    (response) => {
        if (response.status !== 200) return Promise.reject(response.data)
        handleAuthError(response.data.errno)
        handleGeneralError(response.data.errno, response.data.errmsg)
        return response
    },
    (err) => {
        handleNetworkError(err.response.status)
        Promise.reject(err.response)
    }
)
export const Get = <T,>(url: string, params: IAnyObj = {}, clearFn?: Fn): Promise<[any, FcResponse<T> | undefined]> =>
  new Promise((resolve) => {
    axios
      .get(url, { params })
      .then((result) => {
        let res: FcResponse<T>
        if (clearFn !== undefined) {
          res = clearFn(result.data) as unknown as FcResponse<T>
        } else {
          res = result.data as FcResponse<T>
        }
        resolve([null, res as FcResponse<T>])
      })
      .catch((err) => {
        resolve([err, undefined])
      })
  })
export const Post = <T,>(url: string, data: IAnyObj, params: IAnyObj = {}): Promise<[any, FcResponse<T> | undefined]> => {
  return new Promise((resolve) => {
    axios
      .post(url, data, { params })
      .then((result) => {
        resolve([null, result.data as FcResponse<T>])
      })
      .catch((err) => {
        resolve([err, undefined])
      })
  })
}

共同调用点: src/api/index.ts

import { userApi } from "./path/user"
import { shoporderApi } from "./path/shoporder"
export const api = {
	...userApi,
	...shoporderApi
}

接口: src/api/path/user.ts | src/api/path/shoporder.ts

import { Get } from "../server"
export function getUserInfo(id) { ... }
export function getUserName(id) { ... }
export const userApi = {
	getUserInfo,
	getUserName
}
import { Get } from "../server"
function getShoporderDetail() { ... }
function getShoporderList() { ... }
export const shoporderApi = {
	getShoporderDetail,
	getShoporderList
}