## @lx-frontend/ajax

一个规范Ajax使用的框架

## 安装

使用npm:

`npm install @lx-frontend/ajax`

使用yarn:

`yarn add @lx-frontend/ajax`

## 特别说明

### 对微信环境的适配

`@lx-frontend/multi-ajax`是基于`axios`开发的，同时对微信小程序请求引擎做了适配，但是axios并不支持微信小程序环境，在小程序环境下会直接报错，所以`@lx-frontend/ajax`在安装完成后会对`axios`的文件做一些修改以适配微信环境，详见`package.json`的`postinstall`命令。

`@lx-frontend/multi-ajax`并没有将`axios`的代码打包进目标代码，所以`postinstall`命令还需要在使用该库的项目中执行。所以，需要在项目根目录下，`.npmrc`文件中写入`side-effects-cache=false`配置，保证每次安装都能执行postinstall命令。

### 防重功能

`@lx-frontend/multi-ajax`默认具有防重功能，300ms之内，参数完全相同的请求，均复用第一个请求的结果。（暂时没有关闭功能的配置，后面需要再添加）

## 用法

### 创建ajax实例

```js
// 创建ajax实例
import initAjax from '@lx-frontend/ajax'

const ajax = initAjax({
  baseURL: 'api/v1',
  mock: null,
  // ... 自定义配置项 或者 axios支持的配置
})

// 创建一个get请求函数
const getRequest = ajax.get('/get/url', {
  /* 请求配置（自定义 or ajax支持的配置） */
})
// 发送请求
getRequest(
  {
    /* url参数，将以请求参数的形式挂载到url的末尾 */
  },
  {
    /* 请求配置，拥有最高的优先级  */
  }
).then((res) => {})

// 创建一个post请求
const postRequest = ajax.post('/post/url', {
  /* 请求配置（自定义 or ajax支持的配置） */
})
// 发送请求
postRequest(
  {
    /* 请求需要发送的数据 */
  },
  {
    /* 请求配置，拥有最高的优先级  */
  }
).then((res) => {})
```

### 取消请求

```js
// 下面以一个get请求演示如何取消请求

const ajax = initAjax({
  /* ... */
})

// 创建get请求时，配置项
const getRequest = ajax.get('/get/url')
// 发送请求时，配置项传递一个字符串给cancelKey，此时ajax实例上将挂载一个以该字符串命名的函数，此函数就是该请求的取消函数
getRequest(
  {
    /* url参数，将以请求参数的形式挂载到url的末尾 */
  },
  { cancelKey: 'cancelGetRequest' }
).then((res) => {})

// 此处中断请求
ajax.canceller.cancelGetRequest()
```

## options配置

```js
{
  // boolean,是否发送FormData类型的数据，如果为true，则会将data数据转化成FormData形式，且修改请求头header中的Content-Type字段，默认为false，发送json数据
  baseURL
  // 发送FormData格式的数据，仅适用于post类请求
  sendFormData
  // 如果该参数不为空，请求将不会真正发出，而是直接resolve该数据
  mock
  // string，如果请求传递了该参数，则调用ajax.canceller[cancelKey]()将中断该请求
  cancelKey
  // boolean, 出发get请求时，如果已经有一个URL（不包含参数）和method相同且处于pending状态的请求，是否取消上一个请求（比如，切换tab时，需要取消掉上一个tab的请求，避免串数据）
  cancelLastSameUrl
  // ... 其他axios支持的所有配置
}
```

## 拦截器（interceptor）

ajax拦截器同axios的拦截器使用方法完全一样，示例如下：

```js
const ajax = initAjax({
  /* ... */
})

// 请求拦截器注册
ajax.interceptors.request.use(interceptRequest)
// 请求成功或失败拦截器
ajax.interceptors.response.use(interceptSuccess, interceptFail)
```

## TS支持

### 自定义配置的校验和提示功能

共有三个地方可以传递自定义配置

1. 实例化ajax时传递的参数

```js
const ajax = initAjax({
  /* options here */
})
```

此处配置的参数作用于所有的ajax请求

2. 创建ajax请求函数时传递的参数

```js
const getRequest = ajax.get('get/url', {
  /* options here */
})
```

此处的配置优先级高于全局

3. ajax请求函数调用时

```js
const getRequest = ajax.get('get/url', {
  /* options here */
})

getRequest(params, {
  /* options here */
})
```

此处的优先级最高

那么，在编码时，TS如何校验参数传递的是否正确，以及获得配置的提示功能呢？

实际上，ajax的初始化函数`initAjax`函数是一个泛型函数，可以接受两个类型作为参数，其中第一个类型参数便是对自定义参数的描述。

举例如下：

```ts
// 假设我们有cusOpt1和cusOpt2两个自定义参数，类型分别为boolean和string
// 自定义参数的描述
interface CustomOpt {
  cusOpt1: boolean
  cusOpt2: string
}

// 那么，在实例化ajax时
const ajax = initAjax<CustomOpt>({
  /* options here */
})

// 此时，以上三个可以传递自定义参数的地方就拥有了自定义参数的校验和提示功能
```

### 请求返回数据的类型

假设我们有如下请求

```ts
// 创建ajax实例
import initAjax from '@lx-frontend/ajax'

const ajax = initAjax({
  /* ... */
})

// 创建一个get请求函数
const getRequest = ajax.get('/get/url')
// 发送请求
getRequest(params).then((res) => {
  // 如何获得res数据的代码提示功能
})
```

`getRequest`调用发出请求，返回一个Promise，在then回调中获取到了请求的数据，希望在回调函数中能够知道res的结构，获得编码提示功能

同自定义参数的代码提示一样，需要我们自己传入对返回数据的描述

有两个地方可以传递该描述：

1. ajax的实例化函数，该函数的第二个泛型参数便是对返回数据的描述，该描述将作用于全局
2. ajax的实例方法，比如ajax.get，该实例方法可接受一个泛型参数用来描述返回数据的结构，该描述将覆盖全局参数

```ts
// 自定义参数描述
interface CustomOpt {
  cusOpt1: boolean
  cusOpt2: string
}

interface GResShape {
  code: number
  message: string
  data: any
}

// 那么，在实例化ajax时
const ajax = initAjax<CustomOpt, GResShape>({
  /* options here */
})

// 创建一个get请求函数
const getRequest = ajax.get('/get/url')
// 发送请求
getRequest(params).then((res) => {
  // 在这里，ts将res推导为GResShape结构
})

// 上面的方式使得全局的ajax请求都将返回的数据类型推导为GResShape，但是假设个别请求有特殊的返回格式，或者想进一步对上面data的类型进行描述，该怎么办？
// 利用ajax方法可以接收一个泛型参数的能力，获得个性化的返回类型推导能力：

interface LocResShape {
  code: number
  message: string
  data: {
    images: string[]
    pages: number
    totalCount: number
  }
}

// 创建一个get请求函数
const getRequest = ajax.get<LocResShape>('/get/url')
// 发送请求
getRequest(params).then((res) => {
  // 在这里，ts将res推导为LocResShape结构
})
```

### webpack4兼容

在**web**环境下，使用webpack4打包时，webpack4无法识别axios package.json中的browser字段，导致web环境下将node环境代码打包，运行时导致错误。所以这里提供一个webpack4的plugin处理该问题。

项目中的用法如下：

```js
 const {
  Webpack4CustomReplacePlugin,
  axiosRoot,
  axiosUrlMap
} = require('@lx-frontend/multi-ajax/build/webpackPlugin/webpack4-replace-plugin')

// webpack-chain的用法
webpackChain(chain) {
  chain
    .plugin('custom-replace-module')
      .use(Webpack4CustomReplacePlugin, [{
        root: axiosRoot,
        mappings: axiosUrlMap
      }])
}

// plugin配置中的用法
{
  plugins: [
    new Webpack4CustomReplacePlugin({
      root: axiosRoot,
      mappings: axiosUrlMap
    })
  ]
}

```

注意：只在web或小程序项目中使用该插件，node环境下无需使用。

## Axios

通过 axios 和 AxiosTypes 暴露了 axios 的相关内容 (v0.2.10)

```ts
import { axios, AxiosTypes } from '@lx-frontend/multi-ajax'

const normalizeErrorRes = (err: AxiosTypes.AxiosError) => {}

axios.isCancel(err)
axios.isAxiosError(err)
```
