邏輯就是簡單易懂,不要過度的包裝,能用最簡單的就用簡單的完成工作。最好是可替換性,雖然是寫 Vue 專案,同樣的一套程式碼卻不能在 vite, webpack, nuxt 之間相互轉換,這就是 nodejs 專案的宿命。
為了統一所有的程式邏輯,固定將一個 User API 用 $fetch
包裝成 useUsers
,分別有 getUser/getUsers 、 createUser 、 updateUser 、 deleteUser 四大類,下次要加入 Message API,也是包裝成 useMessages
,在 composables 的程式邏輯是一樣的,這樣閱讀起來也方便許多。另外在使用者介面也是相同的程式邏輯。
nuxt 擷取資料的方法有 $fetch 、 useAsyncData 、 useFetch 三種,要包裝成 REST API 最佳選擇就是 $fetch 。
以下就是使用 $fetch 完整的 User API 支援 get, post, delete, update。
// app/composables/useUsers.ts
export const useUsers = () => {
const config = useRuntimeConfig()
const apiBaseUrl = config.public.apiBaseUrl
// GET
const getUser = async (id: number) => {
let dataResponse = null;
let errorResponse = null;
try {
dataResponse = await $fetch(`${apiBaseUrl}/api/users/${id}`)
} catch (error) {
dataResponse = error.data;
errorResponse = {
statusCode: error.statusCode,
message: error.message,
...error.data
}
}
return { dataResponse, errorResponse };
};
const getUsers = async (
offset: number = 0,
limit: number = 10
) => {
let dataResponse = null;
let errorResponse = null;
try {
dataResponse = await $fetch(`${apiBaseUrl}/api/users?offset=${offset}&limit=${limit}`);
} catch (error) {
dataResponse = error.data;
errorResponse = {
statusCode: error.statusCode,
message: error.message,
...error.data
}
}
return { dataResponse, errorResponse };
};
// POST 新增
const createUser = async (user: { name: string; email: string }) => {
let dataResponse = null;
let errorResponse = null;
try {
dataResponse = await $fetch(`${apiBaseUrl}/api/users`, {
method: 'POST',
body: user,
});
} catch (error) {
dataResponse = error.data;
errorResponse = {
statusCode: error.statusCode,
message: error.message,
...error.data
}
}
return { dataResponse, errorResponse };
};
// PUT
const updateUser = async (id: number, user: { name: string; email: string }) => {
let dataResponse = null;
let errorResponse = null;
try {
dataResponse = await $fetch(`${apiBaseUrl}/api/users/${id}`, {
method: 'PUT',
body: user,
});
} catch (error) {
dataResponse = error.data;
errorResponse = {
statusCode: error.statusCode,
message: error.message,
...error.data
}
}
return { dataResponse, errorResponse };
};
// DELETE
const deleteUser = async (id: number) => {
let dataResponse = null;
let errorResponse = null;
try {
dataResponse = await $fetch(`${apiBaseUrl}/api/users/${id}`, {
method: 'DELETE',
});
} catch (error) {
dataResponse = error.data;
errorResponse = {
statusCode: error.statusCode,
message: error.message,
...error.data
}
}
return { dataResponse, errorResponse };
};
const login = async (user) => {
let dataResponse = null;
let errorResponse = null;
try {
dataResponse = await $fetch(`${apiBaseUrl}/api/login`, {
method: 'POST',
body: user,
});
} catch (error) {
dataResponse = error.data;
errorResponse = {
statusCode: error.statusCode,
message: error.message,
...error.data
}
}
return { dataResponse, errorResponse };
}
return { getUser, getUsers, createUser, updateUser, deleteUser, login };
};
Vue 先完成客戶端(前端)使用者 UI ,完成後再用 Python Flask 來完成服務器端(後端)的 REST API。前端完成後可以先測試當遇到後端有問題時必要的處置。
主要目的將呼叫 API 都當作使用它,例如呼叫跟 User 相關的就是使用 useUsers ,裡面會提供 get、post、update、delete 等功能;同樣的原理要使用 Message 就是呼叫 useMessages,這樣的好處還有後端有增加修改,前端盡量減少變動。 useUsers 也可以改成 useUserService 。
Nuxt 有多種方法取得數據 、 、 , useAsyncData 和 useFetch 已經包裝一層,當使用它的時候把放在共用功能元件時候,卻因為錯誤訊息還需要再多加處理,但是衍生出來的問題是數據無法更新,所以最後又多了一個問題。
以下三種程式碼,分別說明回傳資料, useAsyncData 、 useFetch 在 UI 元件中使用很方便,不適合再包裝成 useUsers 。
1.$fetch
// $fetch
try {
const data = await $fetch(`${apiBaseUrl}/api/users`)
} catch (error) {
const errorData = error
}
data // 主機回傳數據
errorData // 主機回傳的錯誤訊息
2.useFetch
// useFetch
const { data, pending, error, refresh } = await useFetch(`${apiBaseUrl}/api/users`)
data.value // vue ref 物件主機回傳數據
error.value // vue ref 物件不是主機回傳的錯誤訊息
// 需要額外處理主機回傳的錯誤訊息
3.useAsyncData
// useAsyncData
const { data, pending, error, refresh } = await useAsyncData(
'name-or-key',
() => $fetch(`${apiBaseUrl}/api/users`)
)
data.value // vue ref 物件主機回傳數據
error.value // vue ref 物件不是主機回傳的錯誤訊息
// 需要額外處理主機回傳的錯誤訊息
useAsyncData 是 useFetch 加上 cache 的加強版本。
$fetch
用 $fetch
包裝 useUsers
(最終版本)
// 使用 `$fetch` 的 `useUsers` (最終版本)
// composables/useUsers.ts
export const useUsers = () => {
const config = useRuntimeConfig()
const apiBaseUrl = config.public.apiBaseUrl
// GET
const getUser = async (id: number) => {
let dataResponse = null;
let errorResponse = null;
try {
dataResponse = await $fetch(`${apiBaseUrl}/api/users/${id}`)
} catch (error) {
dataResponse = error.data;
errorResponse = {
statusCode: error.statusCode,
message: error.message,
...error.data
}
}
return { dataResponse, errorResponse };
};
// POST 新增
const createUser = async (user: { name: string; email: string }) => {
let dataResponse = null;
let errorResponse = null;
try {
dataResponse = await $fetch(`${apiBaseUrl}/api/users`, {
method: 'POST',
body: user,
});
} catch (error) {
dataResponse = error.data;
errorResponse = {
statusCode: error.statusCode,
message: error.message,
...error.data
}
}
return { dataResponse, errorResponse };
};
// 完整版看結論
return { getUser, createUser };
}
使用 useFetch
的 useUsers
在這裡特地命名為
會固定返回多種數據, 當發生錯誤的時候, 會先返回數據, data.value
是 null, error.value
是內建的錯誤訊息, 例如錯誤代碼 400, Bad Request, , 雖然可以用 onResponseError 得到主機給的數據資料, 當 view 接收到第一次的數據時候, 畫面已經顯示出來, 然後 onResponseError 收到數據時更新內容 view 不會再更新畫面, 但是 view 的數據已經更新為新的數據。
// 使用 `useFetch` 的 `useUsers`
// composables/useFetchUsers.ts
export const useUsers = () => {
const config = useRuntimeConfig()
const apiBaseUrl = config.public.apiBaseUrl
// POST 新增
// 發生錯誤流程順序
// 1. error.value 已經完成
// 2. createUser return
// 3. view ui 得到數據
// 4. createUser onResponseError 完成後,產生新的數據
// 5. view ui 得到數據,但是不會更新 UI,只會顯示第一次得到的數據
const createUser = async (user: { name: string; email: string }) => {
console.log("createUser")
let dataResponse = null;
let errorResponse = null
let errorFetch = null;
const { data, pending, error, refresh } = await useFetch(`${apiBaseUrl}/api/users`, {
method: 'POST',
body: user,
onResponseError({ response }) {
if (response && response._data) {
dataResponse = response._data
console.log(response._data)
if (response._data.error) {
console.log(response._data.error)
errorResponse['error'] = response._data.error;
} else if (response._data.message) {
errorResponse['error'] = response._data.message || 'Unknown error';
}
}
},
});
if (error.value) {
console.log(error.value.statusCode)
console.log(error.value.message)
errorResponse = {
statusCode : error.value.statusCode,
message : error.value.message
}
} else if (data.value) {
dataResponse = data.value
}
return { dataResponse, pending, errorResponse, refresh };
};
const getUser = async (id: number) => {
let dataResponse = null;
let errorResponse = null
let errorFetch = null;
const { data, pending, error, refresh } = await useFetch(`${apiBaseUrl}/api/users/${id}`, {
onResponseError({ response }) {
if (response && response._data) {
dataResponse = response._data
console.log(response._data)
if (response._data.error) {
console.log(response._data.error)
errorResponse['error'] = response._data.error;
} else if (response._data.message) {
errorResponse['error'] = response._data.message || 'Unknown error';
}
}
},
});
if (error.value) {
console.log(error.value.statusCode)
console.log(error.value.message)
errorResponse = {
statusCode : error.value.statusCode,
message : error.value.message
}
} else if (data.value) {
dataResponse = data.value
}
return { dataResponse, pending, errorResponse, refresh };
};
return { getUser, createUser };
}
useAsyncData 的流程大約和 useFetch 一樣,不另外說明。
目前後端程式還沒有完成, useUsers ($fetch) 和 useFetchUsers (useFetch) 得到的資料都會是 error code 500
<script setup>
// app/pages/user-demo.vue
const { getUser } = useUsers()
const { dataResponse, errorResponse } = getUser(0)
const { getUser: useFetchGetUser } = useFetchUsers()
const { dataResponse: useFetchDataResponse, errorResponse: useFetchErrorResponse } = useFetchGetUser(0)
</script>
<template>
<div>
<h2>useUsers</h2>
<div v-if="errorResponse">
{{ errorResponse }}
</div>
<div v-else>
{{ dataResponse }}
</div>
</div>
<div>
<h2>useFetchUsers</h2>
<div v-if="useFetchErorResponse">
{{ useFetchErorResponse }}
</div>
<div v-else>
{{ useFetchDataResponse }}
</div>
</div>
</template>
最後不要忘記在 nuxt.config.ts 加入 runtimeConfig 用 useRuntimeConfig
使用這些參數。
// nuxt.config.ts
export default defineNuxtConfig({
//...
runtimeConfig: {
public: {
apiBaseUrl: process.env.API_BASE_URL || 'http://127.0.0.1:5525',
},
},
//...
})
Nuxt
Reactive Data Fetching and Updating in Nuxt 3:取得數據與更新,複雜版本的處理方式(英)
API Factories for Vue.js & Nuxt:可以參考組織設計(英)
nuxt v3.15.4