如果有任何問題或建議,歡迎隨時聯繫我:
各位 Vue 的魔法師們!前幾天我們學習了如何管理組件內部的數據,如何讓數據響應式,如何管理全局狀態。但現實世界中的應用程式,數據往往不是憑空而來,而是需要從遠方的「數據倉庫」(後端 API)中獲取。今天,我們就要學習如何讓你的 Vue 應用程式與後端進行「對話」,優雅地進行 API 串接,並將獲取的數據與狀態管理結合起來,處理數據的「來去」!
想像你的應用程式是一個餐廳,後端 API 就是食材供應商。你需要向供應商下訂單(發送請求),等待食材送達(接收響應),期間可能會有等待(Loading 狀態),也可能遇到食材短缺或送錯貨(Error 狀態)。如何高效、穩定地處理這些環節,是打造一個健壯應用程式的關鍵。
現代前端應用程式大多採用前後端分離的架構。前端負責 UI 呈現和使用者互動,後端負責數據儲存和業務邏輯。兩者之間通過 API (Application Programming Interface) 進行數據交換。
fetch
vs Axios
在 JavaScript 中,有兩種主流的方式來發送 HTTP 請求:
fetch
APIfetch
是瀏覽器內建的 API,它返回一個 Promise,使用起來相對簡潔。
// 使用 fetch 獲取數據
const fetchDataWithFetch = async () => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('Fetch Data:', data);
} catch (error) {
console.error('Fetch Error:', error);
}
};
// 使用 fetch 提交數據 (POST)
const postDataWithFetch = async () => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: 'foo',
body: 'bar',
userId: 1,
}),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('Fetch POST Data:', data);
} catch (error) {
console.error('Fetch POST Error:', error);
}
};
Axios
(推薦)Axios
是一個基於 Promise 的 HTTP 客戶端,可以在瀏覽器和 Node.js 中使用。它提供了更豐富的功能和更友好的 API,是前端開發中非常流行的選擇。
安裝 Axios:
npm install axios
# 或者
yarn add axios
使用 Axios:
// 使用 Axios 獲取數據
import axios from 'axios';
const fetchDataWithAxios = async () => {
try {
const response = await axios.get('https://jsonplaceholder.typicode.com/todos/1');
console.log('Axios Data:', response.data); // Axios 會自動解析 JSON,數據在 response.data 中
} catch (error) {
console.error('Axios Error:', error);
// Axios 的錯誤處理更方便,可以直接訪問 error.response, error.request, error.message
}
};
// 使用 Axios 提交數據 (POST)
const postDataWithAxios = async () => {
try {
const response = await axios.post('https://jsonplaceholder.typicode.com/posts', {
title: 'foo',
body: 'bar',
userId: 1,
});
console.log('Axios POST Data:', response.data);
} catch (error) {
console.error('Axios POST Error:', error);
}
};
為什麼推薦 Axios?
Axios
會自動將響應數據解析為 JSON 物件,無需手動 response.json()
。在發送 API 請求時,數據的獲取是一個非同步過程。這期間,用戶可能會看到空白頁面,或者操作沒有反應。因此,優雅地處理「載入中 (Loading)」和「錯誤 (Error)」狀態至關重要,這能大大提升使用者體驗。
通常,我們會結合 Pinia (或組件自身的響應式狀態) 來管理這些狀態。
<!-- src/components/UserFetcher.vue -->
<script setup>
import { ref } from 'vue';
import axios from 'axios';
const user = ref(null);
const isLoading = ref(false);
const error = ref(null);
const fetchUser = async () => {
isLoading.value = true; // 開始載入,設定為 true
error.value = null; // 清除之前的錯誤
try {
const response = await axios.get('https://jsonplaceholder.typicode.com/users/1');
user.value = response.data;
} catch (err) {
error.value = '載入用戶數據失敗:' + err.message; // 捕獲錯誤
} finally {
isLoading.value = false; // 載入結束,設定為 false
}
};
// 組件掛載時自動獲取數據
import { onMounted } from 'vue';
onMounted(fetchUser);
</script>
<template>
<div>
<h1>用戶數據</h1>
<button @click="fetchUser" :disabled="isLoading">重新載入</button>
<div v-if="isLoading">
<p>載入中,請稍候...</p>
</div>
<div v-else-if="error">
<p style="color: red;">{{ error }}</p>
</div>
<div v-else-if="user">
<p>姓名: {{ user.name }}</p>
<p>Email: {{ user.email }}</p>
</div>
<div v-else>
<p>沒有數據</p>
</div>
</div>
</template>
將 Loading 和 Error 狀態集中到 Pinia Store 中管理,可以讓多個組件共享這些狀態,避免重複邏輯。
// src/stores/user.js
import { defineStore } from 'pinia';
import axios from 'axios';
export const useUserStore = defineStore('user', {
state: () => ({
user: null,
isLoading: false,
error: null,
}),
actions: {
async fetchUser() {
this.isLoading = true;
this.error = null;
try {
const response = await axios.get('https://jsonplaceholder.typicode.com/users/1');
this.user = response.data;
} catch (err) {
this.error = '載入用戶數據失敗:' + err.message;
} finally {
this.isLoading = false;
}
},
},
});
在組件中使用:
<!-- src/components/UserDisplay.vue -->
<script setup>
import { useUserStore } from '../stores/user';
import { storeToRefs } from 'pinia';
import { onMounted } from 'vue';
const userStore = useUserStore();
const { user, isLoading, error } = storeToRefs(userStore);
onMounted(() => {
userStore.fetchUser(); // 組件掛載時觸發獲取數據的 action
});
</script>
<template>
<div>
<h1>用戶數據 (來自 Pinia)</h1>
<button @click="userStore.fetchUser()" :disabled="isLoading">重新載入</button>
<div v-if="isLoading">
<p>載入中,請稍候...</p>
</div>
<div v-else-if="error">
<p style="color: red;">{{ error }}</p>
</div>
<div v-else-if="user">
<p>姓名: {{ user.name }}</p>
<p>Email: {{ user.email }}</p>
</div>
<div v-else>
<p>沒有數據</p>
</div>
</div>
</template>
try...catch
把錯誤「抓」起來,然後告訴用戶發生了什麼事,或是偷偷記下來(console.error),別讓你的應用程式「裸奔」!api.js
),統一管理。這樣,無論是改基地 URL、加 Token,還是處理錯誤,都只需要改一個地方,就像有了「中央控制室」,效率高到爆!// src/services/api.js
import axios from 'axios';
const api = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000, // 請求超時時間
headers: { 'Content-Type': 'application/json' }
});
// 請求攔截器:例如添加認證 Token
api.interceptors.request.use(
config => {
const token = localStorage.getItem('authToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
error => {
return Promise.reject(error);
}
);
// 響應攔截器:例如統一處理錯誤響應
api.interceptors.response.use(
response => response,
error => {
if (error.response) {
// 伺服器返回錯誤狀態碼
console.error('API Error:', error.response.status, error.response.data);
// 根據狀態碼進行處理,例如 401 跳轉登入頁
if (error.response.status === 401) {
// router.push('/login');
}
} else if (error.request) {
// 請求已發出但沒有收到響應
console.error('No response received:', error.request);
} else {
// 其他錯誤
console.error('Error:', error.message);
}
return Promise.reject(error);
}
);
export default api;
然後在你的 Store 或組件中這樣使用:
// import api from '../services/api';
// const response = await api.get('/users');
onMounted
):組件一掛載就發請求,適合頁面首次載入的數據。watch
):當用戶的「點菜單」(搜索詞、篩選器)變了,就重新上菜。記得給「點菜單」加個「防抖」(debounce),別讓廚師(伺服器)忙不過來!// 範例:監聽搜索關鍵字變化來獲取數據
import { ref, watch } from 'vue';
// ...
const searchTerm = ref('');
watch(searchTerm, (newTerm) => {
if (newTerm.length > 2) {
// 觸發 API 請求
// fetchUsers(newTerm);
}
}, { debounce: 300 }); // 可以考慮防抖 (debounce) 來減少請求頻率
Composable
封裝 API 邏輯:
useFetch
或 useApi
)。這樣,無論哪個組件需要「取貨」,直接拿這個工具去用就行,不用每次都重新組裝,省時省力,還能保證品質!// src/composables/useFetch.js (簡化範例)
import { ref } 'vue';
import axios from 'axios';
export function useFetch(url) {
const data = ref(null);
const error = ref(null);
const isLoading = ref(false);
const fetchData = async () => {
isLoading.value = true;
error.value = null;
try {
const response = await axios.get(url);
data.value = response.data;
} catch (err) {
error.value = err;
} finally {
isLoading.value = false;
}
};
return { data, error, isLoading, fetchData };
}
然後在組件中使用:
// import { useFetch } from '../composables/useFetch';
// const { data, error, isLoading, fetchData } = useFetch('https://api.example.com/posts');
// onMounted(fetchData);
今天我們學習了如何在 Vue 應用程式中進行 API 串接,並重點探討了如何優雅地處理數據的載入 (Loading) 和錯誤 (Error) 狀態。我們比較了 fetch
和 Axios
,並推薦使用 Axios
來簡化請求處理。結合 Pinia 進行狀態管理,可以讓你的數據流更加清晰、可預測,大大提升使用者體驗和開發效率。
本日關鍵字回顧
fetch
: 瀏覽器原生 API。Axios
: 基於 Promise 的 HTTP 客戶端,功能更強大。try...catch...finally
: 錯誤處理機制。Axios
的高級功能。onMounted
, watch
等觸發請求的時機。Composable
封裝: 將 API 邏輯抽離成可複用函式。明天,我們將進入另一個實用主題——表單處理與驗證,學習如何讓你的表單既美觀又健壯!