[Nuxt 系列 05] 基于 Nuxt Plugin 和 axios 模块的接口组织模式

3,727 阅读2分钟

之前已经给出了 Organize and decouple your API calls in Nuxt.js译文,其主要思路是通过利用 Nuxt 插件和对 restful 风格接口调用的整体抽象,来使接口的组织和调用趋于合理统一,并且在接口调用环节真的是相当清晰明了啊。当我初次看到那个基于仓库模式的 API 组织抽象时,着实有种眼前一亮的感觉。也是因为未曾真正在实际工作中接触过 restful 风格接口的缘故吧,总之,优雅。

总而言之,我们没有 restful 风格的 api,也无缘受用那种“优雅”喽,但总体的思路依然值得借鉴。而要解决的问题也与常规使用 axios 需要解决的问题大致相同,比如将所有的接口定义统一组织起来方便管理;比如设置全局的拦截器,在请求实际发出前以及响应触达之初做一些全局统一的逻辑处理以及错误处理;比如给出默认的 baseURL、超时时间、请求头字段……

首先,创建一个工厂函数

在这之前确保安装了必要的依赖:npm i -S @nuxtjs/axios。由于不必去考虑 restful 的问题,所以,getpost 已经可以满足我们的需求了。于是有如下代码:

// assets/js/apifactory.js

/**
 * 工厂函数
 * @param $axios axios 实例
 * @param {String} api
 * @param {Object} config 可选的额外配置,如请求头字段等
 * @param data post body 参数
 */ 
export default $axios => (api, config) => ({
  get() {
    return $axios.$get(api, config);
  },
  post(data) {
    return $axios.$post(api, data, config);
  },
});

有了它以后,就要考虑如何将 axios 实例传入的问题,于是

接下来,创建一个 Nuxt Plugin

我们为期命名为 apirepository,并有如下代码:

// plugins/apirepository.js

import apiFactory from '~/assets/js/apifactory';

export default ({ $axios }, inject) => {

  const requestWithAxios = apiFactory($axios);

  const apiRepository = {};

  inject('apiRepository', apiRepository);
}

但以上这个插件要怎样与实际的接口相结合呢?

当我们传入 $axios 并执行 apiFactory 的时候,其实又得到了一个工厂函数,即 requestWithAxios,我们需要为其传入 apiconfig,从而让它产出那个包含了 get 和 post 方法的对象字面量。我们正是要通过它来实现接口的调用。怎么办?

创建一个统一存放 api 的目录

继续工厂函数走起,新建文件 apirepository/user.js:

export default (request, baseURL) => {
  const msgConf = { // 做一些额外的配置
    headers: { 'Content-Type': 'application/json', }, 
    timeout: 30000,
  }
  return {
    index: request(`${baseURL}/user/index`),
    message: request(`${baseURL}/user/msg`, msgConf),
    // ...
  }
}

像上面这样,我们将同一功能区域的接口存放至一个文件内,返回一个对象,暴露给 plugins/apirepository.js:

import userApi from '~/apirepository/user';
// ...

const demoEnv = process.env.DEMO_ENV;
const baseURL = hjxyEnv === 'dev' ? '/api' : 'https://api.xxxx.com';

export default ({ $axios }, inject) => {

  $axios.defaults.timeout = 20000;

  $axios.onRequest(config => {
    // ...
  });

  $axios.onResponse(response => {
    // ...
  });

  $axios.onError(err => {
    // ...
  });

  // 全局注入 api 列表
  const requestWithAxios = apiFactory($axios);
  const apiRepository = {
    user: userApi(requestWithAxios, baseURL),
    // ...

  }

  inject('apiRepository', apiRepository);
}

至此,便算是大功告成了,被注入的 apiRepository 可以在整个项目的任何地方被调用。但是别忘了,还要

在 nuxt.config.js 中配置插件

我们将其配入 nuxt.config.js,并顺便配置一下 proxy,解决本地跨域问题:

// nuxt.config.js
module.exports = {
  // ...

  plugins: [
    '~/plugins/apirepository.js',
  ],

  modules: [
    '@nuxtjs/axios',
    '@nuxtjs/proxy', // 最新文档显示,它不需要手动注册了,但是依赖还是得安装
  ],
  axios: {
    proxy: true,
    https: process.env.DEMO_ENV === 'prod',
  },
  proxy: {
    '/api': {
      target: 'https://api.xxx.com/',
      pathRewrite: {
        '^/api' : '/',
      },
    },
  },

  // ...

}

终于可以调接口了

对于在 asyncDatafetch 方法中的调用:

export default {
  async asyncData({ app }) {
    try {
      const res = app.$apiRepository.home.index.get();
      // ...
      return { ...res.data };
    }catch(err) {}
  },
}

而当需要在单页面组件或 store 中调用时,则 this.$apiRepository.home.index.get();

最后

我们需要根据项目自身的实际情况,在 onRequestonResponseonError 三个拦截器中加入一些特殊的处理逻辑使其能作用域全局。以请求拦截器为例,我们可以在其中统一添加像 token 这样的公共参数;对请求数据做筛查防止像 sql 注入等这样的小骚扰;并且由于 Content-Type 为默认的 application/x-www-form-urlencoded 格式,所以不要忘记对请求的参数做统一的序列化:

import { SQLInjectionDefence } from '~/assets/js/utils';
import qs from 'qs';

// ...

export default ({ $axios }, inject) => {

  $axios.onRequest(config => {
    // ...
    const token = store.getters.token;
    if(config.data === void 0 && token) {
      config.data = { token };
    }else if(token) {
      config.data.token = token;
    }
    config.data = qs.stringify(SQLInjectionDefence(config.data));

    // ...

  });

  // ...

}

总之,打开你的脑洞,为了简化代码,做更多适用于全局的逻辑处理。更多关于 @nuxtjs/axios 的使用细节还是参见其官方文档吧。

实际上 Nuxt 的插件机制可以有很多妙用,许多实际开发中的问题想要得以解决都绕不开它的“羁绊”,比如典型的:如何使统计代码在 Nuxt 应用中生效,如何注入第三方插件等。后续文章做简单介绍~