axios封装与实战(二)

716 阅读4分钟

前言

如果不了解语法请先学这篇 axios 基础语法教学(一)

本文章源码:axios-encapsulation有兴趣可以fork看下

4.jpg

api 到底要放哪?

想像一下,如果今天我想要把热门排行榜显示在每个页面,是不是在每一页都要加上 api?

那如果我今天突然想修改 api 名称,或是参数名称呢?

是不是要去找这支 api 用在哪里,然后再一页一页去更改?

这边列出几个常见到的 RESTful API 直接写在 component 里面的缺点:

  1. 接 API 的时候如果对上多台机器,多个 domain 的时候,在多个组件内部要去找非常的不方便。
  2. 如需要在请求 API 的时候再 headers 里面塞入物件的时候,会重复的去写这个物件,要改的时候会变很麻烦。
  3. 要做 error handling 的时候,也会变得需要写入很多重复的 code 来做错误处理。

建立实例 axios.create

那为了避免这样的状况,我习惯会把 api 从页面独立出来,

让每一页只需要引用 api 方法,带上参数就好,不用管这只 api 是要戳哪个 url。

axios 这边提供了一个方法The Axios Instance,可以使用自定义配置建立实例 (Instance)

const 实例名称 = axios.create({
	//这边放要共用的 request config
});

// 官方范例
const instance = axios.create({
  baseURL: 'https://some-domain.com/api/', //api的前缀网址
  timeout: 1000,                          // api timeout时间
  headers: {'X-Custom-Header': 'foobar'} // api header要带的参数
});

使用范例1:

import axios from "axios";

const domain = "https://bookshelf.goodideas-studio.com";
// 版次
// const apiVersion = 'v1';

const bookAPI = axios.create({
  baseURL: `${domain}/api`,
	// 加上板次
  // baseURL: `${domain}/api/${apiVersion}`,
  headers: {
    "Content-Type": "application/json",
    accept: "application/json",
  },
});

request Config (api 参数)

官方定义了很多种,不过比较常用到的就是那几个,可以参考官方文件

  • baseURL 就是 api 的前缀网址,使用时后面会再串接不同的 request url
  • 在实例中的参数,会跟使用 api 时带的参数合并

以下是这个实例可以使用的方法,比较常用的就是 get、post

axios.request(config)
axios.get(url[, config])
axios.delete(url[, config])
axios.head(url[, config])
axios.options(url[, config])
axios.post(url[, data[, config]])
axios.put(url[, data[, config]])
axios.patch(url[, data[, config]])

在 api 的页面用 axios.create () 建立实例后,要再把它封装成一个方法

里面直接就包含需要的 request config 了

直接看范例1:

//api.js
const instance = axios.create({
  baseURL: 'https://some-domain.com/api/', //api的前缀网址
});
export const getBookList = data => instance.get('/bookList', data);

// 封装成 getBookList后,到vue页面呼叫方法
// BookListPage.vue
import { getBookList } from '../api/api';
getBookList ({
    page: 5,
})
.then(res=> {
    console.log(res);
})

实践一下,使用范例2:

import axios from 'axios';

// 建立一个axios实例
const publicService = axios.create({
  baseURL: 'https://cloud.culture.tw/frontsite', // url = base url + request url
});
// 节庆活动 api
// eslint-disable-next-line import/prefer-default-export
export const getFestival = data => publicService.get('/trans/SearchShowAction.do?method=doFindFestivalTypeJ', data);

// 主题推荐 api
export const getTopic = data => publicService.get('/trans/SearchShowAction.do?method=doFindIssueTypeJ', data);

在vue中使用封装好的api

<script>
import Topic from './Topic';
import { getTopic } from '../api/api'; // 从api.js import 主题推荐的api方法

export default {
  name: 'TopicPage',
  components: { Topic },
  data() {
    return {
      title: '',  // 原本这边都是写死的假资料,直接改成空值即可
      note: '',
      issue: [],
    };
  },
  methods: {
    getTopicData() { // 写一个接资料的方法
      getTopic()     // 呼叫api方法
        .then((res) => {
          this.title = res.data.title;   // 把api回传的值存到data的参数里
          this.note = res.data.note;
          this.issue = res.data.issue;
        })
        .catch((err) => {
          // eslint-disable-next-line no-console
          console.log(err);
        });
    },
  },
  created() {
    this.getTopicData();  // 生命周期函数,在页面初始化时要执行资料
  },
};
</script>

实战封装axios

假设我今天有一个个人网站要做,然后有三个项目的功能要做,三个项目的 API 都各自散落在不同的机器,每个项目都有数个 API 需要使用到

首先开一个名字叫 api 的资料夹,里面新增一个index.js,作为进入点,再来开始分类你的 API。

user 相关的 API

// user.js
const userRequest = axios.create({
  baseURL: 'https://api/user/'
})

export const postUserLogin = data => userRequest.post('/signIn', data)
export const postUserLogout = data => userRequest.post('/signOut', data)
export const postUserSignUp = data => userRequest.post('/signUp', data)

文章相关的 API

// article.js
const articleRequest = axios.create({
  baseURL: 'https://api/article/'
})

export const getArticleItem = () => articleRequest.get('/ArticleItem')
export const postArticleMsg = data => articleRequest.post('/ArticleMsg', data)
export const postArticleLink = data => articleRequest.post('/ArticleLink', data)

查找相关的 API

// search.js
const searchRequest = axios.create({
  baseURL: 'https://api/search/'
})

export const getSearch = data => searchRequest.get(`/Search?searchdata=${data}`)
export const getSearchType = () => searchRequest.get(`/SearchType`)
  1. 透过 axios.create去创造一个实例,再利用变数去接这个实例
  2. 然后在透过这个变数的实例去做get或是post,然后在 export 出去给外面的 js 去 import 就好。

再来我们在 api/index.js 整合这 3 个文件的 API

import { 
  postUserLogin,
  postUserLogout,
  postUserSignUp 
} from "./user.js"
import { 
  getArticleItem,
  postArticleMsg,
  postArticleLink
} from "./article.js"
import { 
  getSearch,
  getSearchType 
} from "./search.js"

export const apiPostUserLogin = postUserLogin
export const apiPostUserLogout = postUserLogout
export const apiPostUserSignUp = postUserSignUp
export const apiGetArticleItem = getArticleItem
export const apiPostArticleMsg = postArticleMsg
export const apiPostArticleLink = postArticleLink
export const apiGetSearch = getSearch
export const apiGetSearchType = getSearchType

接下来我们要使用的时候只要importapi 这个资料夹就好了,像这样就可以确保你 API 来源都是同一个进入点

import { apiGetArticleItem, apiGetSearch } from "../api"; 

实际在 component 内使用会像这样。

import { apiGetArticleItem, apiGetSearch } from "../api"
import { ref, onMounted } from "vue"
export default {
   setup() {
       const getData = async () => {
           try {
               const item = await apiGetArticleItem()
               const search = await apiGetSearch()
             		
               // 其他的处理
             
           } catch (err) {
             	console.error(err)
           }
       }
        
       onMounted(()=> {
         getData()
       })

       return {}
   },
}

这样的方式一样可以在 Vuex 或是透过 composition api 来引入使用。

import { apiGetArticleItem, apiGetSearch } from "../api"
import { createStore } from 'vuex'

const store = createStore({
    state () {
        return {
          item: [],
          search: {},
        }
    },
    actions: {
      async getDate({commit}, newId) {
        try {
          const item = await apiGetArticleItem()
          const search = await apiGetSearch()
          commit("GET_DATA", { item: item.data, search: search.data })
        } catch (err) {
          console.error(err)
        }
      },
    },
    mutations: {
      GET_DATA (state, payload) {
        state.item = payload.item
        state.search = payload.search
      } 
    }
})

可以看出封装axios有如下好处

  1. 你可以确保你的 api 来源都是同一个进入点进来的,所以即便你今天在许多 js 里面都去呼叫 api,最后管理的只会有一支,你要去做 domain 的修改或是新增都会方便不少
  2. 透过 axios.create所创造出来的实体你可以透过变数去重新给予这个实体一个新的名字,然后透过命名规则的方式来区分你的 api 来分类,在每个 import 的 js 档案只要透过命名规则就可以清楚知道这个 api 目前是从哪个机器跟分类的。
  3. 因为这样可以减少拢长的 api url,增加业务逻辑上面 code 的整洁度,而且 axios 回传回来的是一个 promise 的物件,所以我们可以搭配 Async / Await,减少使用 .then 还有.catch 等方法,使用try catchcode 会更简洁。
  4. 如果要统一对 axios 做 interceptors 的话也会方便很多

拦截 request 与 response实战

axios 有提供拦截 request 与 response 的方法,可以让我们在发送 request 前或是 response 回来之后统一的去做一些处理,像是

  • 送出资料的时候检查有没有 token
  • 资料回来了检查 API 是否有错误
  • status code 是否不是 200 要做 Error handling

我们可以在你 axios.create 那个档案里面这样用,以 search.js 为例

// search.js
const searchRequest = axios.create({
  baseURL: 'https://api/search/'
})

// 拦截 API request 的请求
searchRequest.interceptors.request.use(request=> {
      // API送出前可以做最后的处理
      request.headers['Authorization'] = "你的任何想塞进去的东西";
      return request;
}, error=> {
      // 如果送出前失败了,这边就可以做一些处理
      return Promise.reject(error);
});

// 拦截 API response 的回传
searchRequest.interceptors.response.use(response  => {
      // 这边可以对回来的资料先进行验证处理,再来决定要不要把资料给吐出去
      return Promise.resolve(response);
}, error => {
      // 这边当API发生错误的时候就可以处理 Error handling
      return Promise.reject(error.response.data);
})

export const getSearch = data => searchRequest.get(`/Search?searchdata=${data}`)
export const getSearchType = () => searchRequest.get(`/SearchType`)

对于这样的统一处理 Error handling 我可以说是非常的喜欢,大家可以试试看

参考资料

小声说

如果喜欢请给个赞或星星喔,谢谢~