axios-service

combine with axios to make xhr more easlier and standard and robust to use when coding

Usage no npm install needed!

<script type="module">
  import axiosService from 'https://cdn.skypack.dev/axios-service';
</script>

README

axios-service

npm version build status

安装

# dependencies
npm install axios-service -S

# devDependencies
npm install axios-service -D

全局配置

注意: 全局设置为全局通用的配置, 需要再入口处设置, 下面配置项也可以针对某个请求单独设置

配置参数说明

import axios from 'axios'
import axiosService from 'axios-service'

// 配置示例
axiosService.init(axios, {
  // `defaults` 是基础设置, 是透传到axios.defaults的配置
  defaults: {
    // `withCredentials` 跨域允许携带cookie
    withCredentials: true
    // ...
  },
  // `requestDefaults` 是axiosService的请求代理中关于`response.data`中状态检测的配置项
  requestDefaults: {
    // `dataKey` server端返回值数据的key
    // 如果这个dataKey不存在, 会将http请求返回的data字段直接返回(不是服务端的data)
    dataKey: 'data',
    // `msgKey` server端返回值消息的key
    msgKey: 'msg',
    // `codeKey` server端返回值状态码的key
    codeKey: 'code',
    // `successCode` server端请求成功的状态, 注意: 此为response.data下该接口请求成功状态码, 非浏览器中http请求返回的成功状态(200)
    successCode: 0
  }
})

参数介绍

getRequestsByRoot参数介绍

get参数介绍

restFulGet参数介绍

getMockDecoratorByEnv参数介绍

getMessageDecorator参数介绍

service-decorators装饰规范

更多apis用法及使用示例

apis配置示例

注意: 下面的root参数应该从配置项中根据环境来获取, 这里仅仅是演示


import { service, getRequestsByRoot } from 'axios-service'

// root: 请求跟路劲, 这里默认都是全局, 不走axios.create
const { get, post, postXForm, postXFormData, postXFormString } = getRequestsByRoot({ root: 'http://127.0.0.1:3801/' })

// isCreateInstance 表示axios.create创建新的实例
const { get: peGet, post: post, restFulGet: peRestFulGet } = getRequestsByRoot({ root: 'https://api.github.com/', isCreateInstance: true })

export const getInfo = get('api/aladdin/login/info')

// 自定义key
export const getPeInfo = peGet('api/v2/user/login', {
  msgKey: 'msg',
  codeKey: 'status',
})

// dataKey为null时, 会直接将http请求中的data字段返回
export const getInfoNoDataKey = get('api/getInfoResponseString', { dataKey: null })

// 自定义config,
export const getPeInfo = peGet('api/v2/user/login', {
  msgKey: 'msg',
  codeKey: 'status',
}, {
  // 该值为自定义的, axios-service不会处理, 该config值会透传到 axios中interceptors中的第一个参数
  autoLoading: false
})

// 扩展函数参数
// 如: post请求, url上带query string
export const postPeInfo = (params, data) => post('api/v2/user/login', null, {
  params,
  data
})()

export const postXFormData = (params, data) => postXFormData('api/v2/user/login', null, {
  params,
  data
})()


export const postXFormString = (params, data) => postXFormString('api/v2/user/login', null, {
  params,
  data,
  // 该值为自定义的, axios-service不会处理, 该config值会透传到 axios中interceptors中的第一个参数
  autoLoading: false
})()


// 扩展函数Promise, 适合异步获取请求参数
const peUserLoginPost = pePost('api/v2/user/login')
const atomPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve({ uid: 123, sid: 123 })
  })
})
const asyncAddUidToApi = fn => params => atomPromise.then(({ uid, sid }) => fn({ ...params, uid, sid }))

export const asyncPostPeInfo = asyncAddUidToApi(peUserLoginPost)

具体使用

新版的servce在api中配置完即可直接使用, 不需要再次S.extend之类的, 也不需要从Service中获取具体的请求函数

import { getInfo } from './apis'

// 第一个参数就是请求的参数, 第二个参数是额外的配置
getInfo({
  name: '12312',
}, {
  headers: {
    ticket: 'ticket',
  }
})
  // 第一个then是成功的回调, 是通过successCode和codeKey一起判断,
  .then(({ data, code, msg }) => {
    // 这里的 data, code, msg这三个字段, 就是配置时候传入的dataKey, codeKey, msgKey
    console.log(code, msg, data)
  }, (e) => {
    console.log(e)
  })

restFul配置

import { getRequestsByRoot } from 'axios-service'

const { get: peGet, post: pePost, restFulGet: peRestFulGet } = getRequestsByRoot({ root: 'http://api.demo.cn/' })

// 注意: url中需要再次配置的, 用$开始, 如$page_size, 即表示这个位置需要传入 page_size这个参数的值
export const getHost = peRestFulGet('api/v2/tree/tagstring/cop.inke/page_size/$page_size/page_index/$page_index/hosts', {
  msgKey: 'msg',
  codeKey: 'status',
})
import { getInfo, getPeInfo, getHost } from './apis'

// 第一个参数是urlData, 即restFul中需要替换url的值, 拼接的过程serviceProxy会处理
// 第二个参数是具体的data
getHost({
  page_size: 10,
  page_index: 1
}, {
  name: 'test'
})
  .then(({ data }) => {
    console.log(data)
  }, (e) => {
    console.log(e)
  })

mock装饰器

axios-service与axios-mock-adapter并没有冲突, 只是

  1. axios-mock-adapter一旦使用, 全局所有用axios请求的接口都要进行mock, 如果大型项目, 每个接口都需要维护mock工作量成本过大, 本库提供的方案可以针对需要mock的接口单独做简单mock, 可灵活处理
  2. 本库提供一个保险机制, 在getMockDecoratorByEnv传入一个Boolean值, 如果是走mock, 如果是则走针接口, 可以保证在生产环境不会被mock干扰

本库提供两个方案, 一个是函数包裹, 一个是类的装饰器方案. 如果用类的方案,需要添加class的decorators解析器babel-plugin-transform-decorators

使用案例:

import { getMockDecoratorByEnv } from 'axios-service'

// 传入的值为: 是否为开发环境. 该变量是做一层保障, 在[生产模式]会走直接口, [开发环境]走mock数据, 以防忘记关闭mock而打包上线, 导致线上请求mock数据的情况
// web项目
const mockDecorator = getMockDecoratorByEnv(process.env.NODE_ENV === 'development')
// react-native项目
const mockDecorator = getMockDecoratorByEnv(__DEV__)


// 注意: 从1.3.1起, 直接暴露了mockDecorator函数, 不需要通过getMockDecoratorByEnv来创建
import { mockDecorator } from 'axios-service'


// mock相关逻辑
const mockGetInfo = mockDecorator((...args) => {
  // 这样可以在production构建阶段, 剔除掉if内部的mock代码, 减少线上包体积, 下面代码构建结果如下: if(false) { var mockjs; }
  if (process.env.NODE_ENV === 'development') {
    const mockjs = require('mockjs')
    return Promise.resolve({
      'code': 0,
      'message': 'success',
      'data': {
        'name': '李宝旭 mock',
        'name_en': 'libaoxu by mock',
        'email': 'libaoxu520@gmail.com',
        'github': 'https://github.com/libaoxu'
      },
      'msg': 'success'
    })
  }
})

// 包裹函数的写法
export const getInfoWithMock = mockGetInfo(get('api/getInfo')


// 类装饰器的写法
class Apis {
  @mockGetInfo
  getInfoWithMock = get('api/getInfo')
}

export default new Apis()

消息装饰器

消息装饰器是一个工具函数, 与axios-service没有关联, 可装饰任何返回Promise的函数, 该装饰器更多提供的只是一个装饰的思路, 开发者可自由扩展自定义装饰器, 如异步参数依赖, 单例, loading等等

import { getMessageDecorator, serviceHocs } from 'axios-service'
// 本库并不强依赖redux, 其他具有compose功能的库都可以用, 如: ramda
import { compose } from 'redux'
// const { compose } = require('ramda')

const { getErrorMsg } = serviceHocs
const { get, post, , postXFormData, postXFormString } = getRequestsByRoot({ root: 'http://127.0.0.1:3801/' })

/**
 * 实际项目中应该替换 success 和 erorr 对应的ui函数
 */
const messageDecorator = getMessageDecorator({ success: alert, error: alert })
const requestFailMsg = getErrorMsg('请求失败, 请重试!')

/**
 * 单个装饰器
 */
class Apis {
  @messageDecorator({ successMsg: '获取用户信息请求成功', errorMsg: '获取用户信息请求失败' })
  getInfo = get('api/getInfo')
}

/**
 * 多个装饰器
 */
class Apis {
  @messageDecorator({ successMsg: '获取用户信息请求成功', errorMsg: (error) => (error && error.msg) || '请求失败' })
  @mockSuccess
  getInfo = get('api/getInfo')

  /**
   * 函数式写法
   */
  getInfoFunc = compose(
    messageDecorator({ successMsg: '请求成功', errorMsg: requestFailMsg })
    mockSuccess
  )(get('api/getInfo'))
}

未使用消息装饰器接口的写法

// 如果api.getInfo被多次调用, 每次调用都需要写toast相关逻辑
api.getInfo().then(() => {
  toast.success('请求成功')
}, () => {
  toast.error('请求失败')
})

api.getInfo().then(() => {
  toast.success('请求成功')
}, () => {
  toast.error('请求失败')
})

api.getInfo().then(() => {
  toast.success('请求成功')
}, () => {
  toast.error('请求失败')
})

使用消息装饰器的用法

// 该接口使用多次之后, 不需要每次都进行消息提示
api.getInfo()

api.getInfo()

api.getInfo()

更多装饰器

主要包含setDataDecoratesetParamsDecoratedelayDecorate等装饰器, 下面是具体用法:

其中setDataDecorate代替原setCustomDataWrapper高阶函数用法, setParamsDecorate代替原setCustomParamsWrapper高阶函数用法

其中delayDecorate这里是直接给web端用的, 内置production保护机制, 如果是rn端和小程序端, 请参考中getDelayDecorate用法

import { serviceHocs, getRequestsByRoot } from 'axios-service'
import { messageDecorator, requestFailErrMsg } from './service-hocs'
import { mockGetInfo } from './apis-mock'

const { requestOptsWrapper, setDataDecorate, setParamsDecorate, delayDecorate } = serviceHocs
const { get: baseGet, post: basePost } = getRequestsByRoot({ root: 'http://127.0.0.1:3801/' })

const responseKeys = {
  msgKey: 'msg_custom',
  codeKey: 'code_custom',
  successCode: 0
}

const customData = { name: 'libx', birth: '1996' }

const customParams = { uid: 123, sid: 456 }

const get = requestOptsWrapper(baseGet, responseKeys)

const post = requestOptsWrapper(basePost, responseKeys)

class Apis {
  getInfoCustom = get('/api/getInfoCustom')

  postInfoCustom = post('/api/postInfoCustom')

  // 将customParams 固定到请求的query string中
  @setParamsDecorate(customParams)
  getInfoWithParamsDecorator = get('/api/getInfoCustom')

  // 将customData 固定到请求的body体中
  @setDataDecorate(customData)
  getInfoWithDataDecorator = post('/api/getInfoCustom')

  @setParamsDecorate(customParams)
  @setDataDecorate(customData)
  getInfoWithParamsAndDataDecorator = post('/api/getInfoCustom')

  @messageDecorator({ successMsg: '混合装饰器请求成功', errorMsg: requestFailErrMsg })
  @mockGetInfo
  // 延时3s, 注意: 这里是web端, 内置production保护机制, 如果是rn端和小程序端, 请参考中`getDelayDecorate`用法
  @delayDecorate(3000)
  @setParamsDecorate(customParams)
  @setDataDecorate(customData)
  getInfoWithMoreDecorators = post('/api/getInfoCustom')
}

export default new Apis()

更多详细使用请参考: apis-request-decorators

其他高阶函数

requestOptsWrapper

import { serviceHocs, getRequestsByRoot } from 'axios-service'
import { compose } from 'redux'

const { requestOptsWrapper } = serviceHocs
const { get: baseGet, post: basePost, postXForm } = getRequestsByRoot({ root: 'http://127.0.0.1:3801/' })

const responseKeys = {
  msgKey: 'msg_custom',
  codeKey: 'code_custom',
  successCode: 0
}

const get = requestOptsWrapper(baseGet, responseKeys)

const post = requestOptsWrapper(basePost, responseKeys)

/**
 * before:
 * export const getInfoCustom1 = get('/api/getInfoCustom1', responseKeys)
 * export const getInfoCustom2 = get('/api/getInfoCustom2', responseKeys)
 * export const getInfoCustom3 = get('/api/getInfoCustom3', responseKeys)
 * after:
 * 将每次都传入的requestOpts给柯里化起来
 */
export const getInfoCustom1 = get('/api/getInfoCustom1')
export const getInfoCustom2 = get('/api/getInfoCustom2')
export const getInfoCustom3 = get('/api/getInfoCustom3')

// post同理
export const postInfoCustom1 = post('/api/postInfoCustom1')
export const postInfoCustom2 = post('/api/postInfoCustom2')
export const postInfoCustom3 = post('/api/postInfoCustom3')

setCustomDataWrappersetCustomParamsWrapper

这两个函数已经🚫 DEPRECATED不建议使用, 请使用更多装饰器中的装饰器来解决相同场景的业务

import { serviceHocs, getRequestsByRoot } from 'axios-service'
import { compose } from 'redux'

const { requestOptsWrapper, setCustomDataWrapper, setCustomParamsWrapper } = serviceHocs
const { get: baseGet, post: basePost, postXForm } = getRequestsByRoot({ root: 'http://127.0.0.1:3801/' })

const responseKeys = {
  msgKey: 'error_msg',
  codeKey: 'dm_error',
  successCode: 0
}

const customData = { name: 'libx', birth: '1996' }

const customParams = { uid: 123, sid: 456 }

const get = requestOptsWrapper(baseGet, responseKeys)

const post = requestOptsWrapper(basePost, responseKeys)

// basic
const composeGet = compose(
  fn => setCustomDataWrapper(fn, customData),
  fn => requestOptsWrapper(fn, responseKeys),
)(baseGet)

// or
const requestHoc = compose(
  fn => setCustomDataWrapper(fn, customData),
  fn => requestOptsWrapper(fn, responseKeys),
  fn => setCustomParamsWrapper(fn, customParams),
)

const composePost = requestHoc(post)
const composeGet = requestHoc(get)

export const getInfoCustom = get('/api/getInfoCustom')

export const postInfoCustom = post('/api/postInfoCustom')

/**
 * 混合 setCustomDataWrapper 和 requestOptsWrapper 两种预置
 */
export const getInfoCustomComposedData = composeGet('/api/getInfoCustom')

/**
 * 混合 requestOptsWrapper 和 setCustomParamsWrapper 两种预置
 */
export const postInfoCustomComposedParamsAndData = composePost('/api/postInfoCustom')

更多详细使用请参考: api-request-custom

创建新实例

配合axios.create使用, 创建新的axiosService实例, 更多案例详情, 请查看使用案例axios-service-create

const instance = axios.create()
const customService = axiosService.create(instance, {
  defaults: {
    withCredentials: true
  },
  requestDefaults: {
    // server端请求msg
    msgKey: 'message',
    // server端数据的key
    dataKey: 'data',
    // server端请求状态的key
    codeKey: 'code',
    // server端请求成功的状态
    successCode: 1
  }
})

instance.interceptors.request.use(function (e) {
  console.log('axiosCreate 独立实例拦截器: ', e)
  return e
})


const { getRequestsByRoot } = customService
const { get, post, postXForm } = getRequestsByRoot({ root: 'http://127.0.0.1:3801/' })

export const axiosServiceCreateGetInfo = get('api/getCode1Info', null, {
  xxx
})

更多实际演示请查看代码

examples

启动命令示例

# api实际演示案例
npm run example

# 模拟api接口的node服务
npm run apiserver

项目构建

npm run build

项目发布

npm run pub

项目规范

  1. readme: 组件所涉及配置、方法和基础使用一定要详细和正确, 让开发人员复制过来就能用
  2. example: 一定要有示例, 其他开发人员才能更容易看懂
  3. src: 具体逻辑放到src(source)下面
  4. build: 是构建和发布相关
  5. dist: 是构建之后的目录, 支持dev和prod双模式
  6. package.json
  • scripts: 抽象不同功能, 一定要自动化
  • main: 指定node_modules中依赖的入口文件
  1. eslint: 业务代码要有规范, 通用项目更要有代码规范,
  2. changelog: 每次项目迭代所做的修改一定做好记录
  3. test: 通用的组件肯定是业务无关的, 最好要有单元测试
  4. travis:持续集成