iT邦幫忙

2021 iThome 鐵人賽

DAY 13
2
Modern Web

[ 重構倒數30天,你的網站不Vue白不Vue ] 系列 第 13

[重構倒數第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


上一篇
[重構倒數第19天] - i18n什麼的交給前端來處理吧(二) Vue3 載入Vue-i18n
下一篇
[重構倒數第17天] - 重組資料格式減少不必要的迴圈執行
系列文
[ 重構倒數30天,你的網站不Vue白不Vue ] 32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言