[重构倒数第18天] - 我如何再Vue里面使用axios有效管理API

前言

该系列是为了让看过Vue官方文件或学过Vue但是却不知道怎麽下手去重构现在有的网站而去规画的系列文章,在这边整理了许多我自己使用Vue重构很多网站的经验分享给读者们。

我们在开发网页的时候一定会处理到 RESTful API 的串接,绝大多数我们都会将 RESTful API 给写在 component 里面去做操作,但是这样的做法在具有一定规模的平台上面是非常难以管理跟维护的。

https://ithelp.ithome.com.tw/upload/images/20210913/20125854H6kup1EwO9.png

在这边我选用 axios 来作为我处理非同步的工具。

import { ref, onMounted } from "vue"
import axios from "axios"
export default {
   setup() {
       const Data = ref([]);
     
       onMounted(()=> {
           axios.get('https://test.api/api/item')
             .then(res => {
                Data.value = res;
           	  })
             .catch(error => {
                console.log("handle error =>", error);
             })
       })
 
       return {
         Data
       };
   },
}

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

  1. 串接 API 的时候如果对上多台机器,多个 domain 的时候,在多个组件内部要去找非常的不方便。
  2. 如需要在打 API 的时候再 headers 里面塞入物件的时候,会重复的去写这个物件,要改的时候会变很麻烦。
  3. 要做 error handling 的时候,也会变得需要写入很多重复的 code 来做错误处理。
  4. 如果今天要修改 API 的使用方式,或是换套件来处理 API,这样要改也会很麻烦。

所以实际上专案要管理 API,最後就是把 API 的部分给抽离出来,不要直接写在里面,你会说我会把前面的domain 拉出去变成变数或是集中把所有的 API 放入阵列里面再去利用索引呼叫做管理,甚至可以统一拉到 Vuex去管理使用 API 等方法,但我都觉得这类的方法都不会是最佳管理 API 的最佳作法,相信我! 这类的管理的方式我做了许多的尝试…,不同 domain 的 API 管理上面相对来说相对难度也会提高,除了程序可读性以外,还要好维护,考量到许多因素後,我来分享一下我是怎麽做的。

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

假设一个情境

我今天有一个部落格平台要做,然後有三个项目的功能要做,三个项目的 API 都各自散落在不同的机器,每个项目都有数个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

接下来我们要使用的时候只要import api 这个资料夹就好了,像这样就可以确保你 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
      } 
    }
})
  1. 你可以确保你的 api 来源都是同一个进入点进来的,所以即便你今天在许多 js 里面都去呼叫 api,最後管理的只会有一支,你要去做 domain 的修改或是新增都会方便不少。
  2. 透过 axios.create所创造出来的实体你可以透过变数去重新给予这个实体一个新的名字,然後透过命名规则的方式来区分你的 api 来分类,在每个 import 的 js 档案只要透过命名规则就可以清楚知道这个 api 目前是从哪个机器跟分类的。
  3. 因为这样可以减少拢长的 api url,增加业务逻辑上面 code 的整洁度,而且 axios 回传回来的是一个 promise 的物件,所以我们可以搭配 Async / Await,减少使用 .then还有.catch等方法,使用 try catch code会更简洁。
  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 可以说是非常的喜欢,大家可以试试看。

Axios GitHub : https://github.com/axios/axios

同场加映

之前我有在社群分享过我在规划专案使用 Vuex 跟 API 的架构,里面也有提到独立管理 API 的部分,分享一下简报给各位读者当参考。
https://slides.com/mikecheng1208/deck-1

Mike Vue

那如果对於Vue3不够熟的话呢?

Ps. 购买的时候请登入或注册该平台的会员,然後再使用下面连结进入网站点击「立即购课」,这样才可以让我获得更多的课程分润,还可以帮助我完成更多丰富的内容给各位。

我有开设了一堂专门针对Vue3从零开始教学的课程,如果你觉得不错的话,可以购买我课程来学习
https://hiskio.com/bundles/9WwPNYRpz?s=tc

那如果对於JS基础不熟的朋友,我也有开设JS的入门课程,可以参考这个课程
https://hiskio.com/bundles/b9Rovqy7z?s=tc

订阅Mike的频道享受精彩的教学与分享

Mike 的 Youtube 频道
Mike的medium
MIke 的官方 line 帐号,好友搜寻 @mike_cheng


<<:  Chapter1 - 补充 CORS + autoplay政策 + requestAnimeFrame致命缺点

>>:  [Java Day02] 我的第一支Java程序 & 程序卡与范例档的使用

【ModernWeb'21 VIRTUAL EVENT 议程整理】(非官方)

ModernWeb 是台湾网站技术人的年度盛会,有许多 Web 技术高手分享经验, 今年同样采线上的...

[Linux] 让程序在背景执行

订阅patreon即可看到更多文章 https://www.patreon.com/wade3c ...

Day02_话说从头~ISO27001干嘛用的~能吃吗~XD"

我可以吃,啊不对,是ISO27001可以吃,更不对XDDD"是赚来的钱钱可以买好吃的~(冷...

Day 15 : 机器学习介绍

前半段讲python讲得差不多惹XDD 终於进入机器学习篇章(打开全新的一页的感觉),接着让我们好好...

Day-20 使用 @apply 制做组件

昨天威尔猪示范了按钮的制作,很多小伙伴应该看完就崩溃了,样式设计很弹性没错,但写一个小小的按钮 +...