# 🏅 Day 10 - useFetch 與 useAsyncData
## 今日學習目標
- 學習如何使用 `useFetch` 與 `useAsyncData`,包括帶入參數和使用回傳參數的方法。
- 理解這兩個方法都是基於 `$fetch` 進行封裝。
## 前言
在 Day9 ,我們學習了 `$fetch` 的基本使用方法,並了解 `$fetch` 是基於 `ofetch` 套件所提供的功能,使得 `$fetch` 可以使用 `ofetch` 定義的結構,例如 `query`、`baseURL`、參數、攔截器等。
```jsx
const resonnse = await $fetch(url, {
method: "",
body: {},
// 攔截器
onRequest({ request, options }) {
// 舉例 : 設置 request headers
options.headers = options.headers || {};
options.headers.authorization = `Bearer token XXX`;
},
```
`$fetch` 支援在伺服器端與客戶端處理 HTTP 請求,因此可以在 SSR 與 CSR 階段都被呼叫執行。在請求資料時可能會造成伺服器端與客戶端各發出一次 HTTP 請求的問題。為了避免這個問題,官方建議我們使用 `useFetch` 或 `useAsyncData` 方法先在伺服器端取得資料,然後再傳輸到客戶端。
## useAsyncData
`useAsyncData` 是 Nuxt 3 用於**伺服器端**發出非同步請求的 Composable,會由 Auto Imports 自動匯入至每個 .vue 元件。在**伺服器端**發出請求並取得資料後,將資料狀態傳輸到客戶端。
使用 `useAsyncData` 時必須帶入一個**唯一值的 key** 以及一個回傳 `$fetch` 的**非同步函式** (`handler`) 。除此之外,還提供了在伺服器端發出請求的參數選項。
`useAsyncData` 發出 API 請求的主體在非同步函式中,預設是使用 `$fetch()` 發出請求,並且允許在 `$fetch` 請求中攜帶請求的參數(格式與 `$fetch` 相同,沒有差異)。
```jsx
await useAsyncData(
key, // <== 唯一的 key 值
() => $fetch('API URL', { // <== 非同步函式並且回傳 $fetch
// $fetch 發出請求要帶入的參數,用法沒有差異
query: { /*...*/ },
onRequest({ request, options }){ /*...*/ }
}), {
// <== useAsyncData 在伺服器端發出請求的參數
}
)
```
### 使用方式
接下來將針對以下參數進行介紹,其餘部分可以至 [官方文件](https://nuxt.com/docs/api/composables/use-async-data#params) 閱讀
```jsx
await useAsyncData(
key,
() => $fetch(url,{ /* $fetch 的參數... */ }),
{
/* useAsyncData 提供的參數 */
}
)
```
- key : 唯一的 key 值,不能夠重複 。用於**防止在伺服器端和客戶端發出兩次相同的請求**。如果沒有提供 key 值,`useAsyncData` 會自動產生一個 key 。提供 key 值後,可以使用 [refreshNuxtData(key)](https://nuxt.com.cn/docs/api/utils/refresh-nuxt-data) 重新觸發請求,從伺服器重新取得資料並更新頁面。例如下方的範例,使用 `useAsyncData` 取得資料後,可以透過按鈕的點擊事件觸發 `refreshNuxtData("getRandomuser1");` 重新取得資料並更新畫面。
```html
<!-- 對應至範例的 /pages/index.vue -->
<script setup>
// 1. 有提供 key 就可以使用 refreshNuxtData() 資料發更新
const userData = ref({});
const { data } = await useAsyncData("getRandomuser1", () =>
$fetch("https://randomuser.me/api/")
);
// 透過 refreshNuxtData() 請求 API
const refresh = () => {
refreshNuxtData("getRandomuser1");
}
</script>
<template>
<hr />
<h2>使用 key 搭配 refreshNuxtData() 觸發資料更新</h2>
<img
:src="data.results[0].picture?.large"
:alt="`${data.results[0].name?.first} ${data.results[0].name?.last}`"
/>
<h1>{{ data.results[0].name?.first }} {{ data.results[0].name?.last }}</h1>
<h3>Email: {{ data.results[0].email }}</h3>
<button @click="refresh">更新資料</button>
</template>
```
- `() => $fetch('',{})` : 用於發出請求的非同步函式 (`handler`),必需將 `$fetch` 請求的 response 回傳給 useAsyncData 。另外,發出請求的 $fetch 也可以在第二個參數放入參數與攔截器,例如 :
```jsx
await useAsyncData(
key,
() =>
$fetch(url, {
method: "",
body: {},
onRequest({ request, options }){}
}),
)
```
- 第三個參數物件是專門為 `useAsyncData`提供的參數,設定在伺服器端處理資料的細部功能:
- server:是否在伺服器端處理資料的請求,預設為 `true` 。當 `server` 設定為 `false` 時,請求只會在客戶端執行。
- transform:是一個函式,用於修改 `handler` 請求結果並將其回傳。以下方程式碼為例,在 transform 函式中使用解構把 response.results 陣列內的物件取出,賦予在 resultObject 變數上並回傳給 userTransformData 。
```html
<!-- 對應至範例的 /pages/index.vue -->
<script setup>
// 2. 用 transform 函式修改 handler 請求的結果
const { data: userTransformData } = await useAsyncData(
"getRandomuser2",
() => $fetch("https://randomuser.me/api/"),
{
transform: (response) => {
// 修改 response 的結果,取出並回傳物件結構
const [resultObject] = response.results;
// 一定要 return 資料,否則在 userTransformData 會取不到資料
return resultObject;
},
}
);
</script>
<template>
<h2>用 transform 函式修改 handler 請求的結果</h2>
<img
:src="userTransformData.picture?.large"
:alt="`${userTransformData.name?.first} ${userTransformData.name?.last}`"
/>
<h1>
{{ userTransformData.name?.first }} {{ userTransformData.name?.last }}
</h1>
<h3>Email: {{ userTransformData.email }}</h3>
<hr />
</template>
```
- pick: 為陣列格式,填入屬性的名稱。只從 `handler` 函式回傳的資料中取出指定屬性名稱的資料。以下方程式碼為例,在 pick 參數的陣列中填入 “info” ,userPickData 取得的資料就只會包含 info 屬性的物件資料。
```html
<!-- 對應至範例的 /pages/index.vue -->
<script setup>
// 3. 用 pick 參數從 handler 函式回傳的資料中,從物件取出 info 屬性的資料
const { data: userPickData } = await useAsyncData(
"getRandomuser3",
() =>
$fetch("https://randomuser.me/api/", {
query: {
seed: "be579ffd62ede3ce",
},
}),
{ pick: ["info"] }
);
</script>
<template>
<pre>
{{ userPickData }}
</pre>
<!--
userPickData 結果會是:
{
"info": {
"seed": "be579ffd62ede3ce",
"results": 1,
"page": 1,
"version": "1.4"
}
} -->
</template>
```
### 回傳值
useAsyncData 執行後回傳的值包括 `data`、`status`、`error`、`refresh` 和 `clear` 。我們可以使用 [ES6 解構賦值](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#%E7%89%A9%E4%BB%B6%E8%A7%A3%E6%A7%8B) 將這些值解構出來使用。其中 **`data` 、 `status` 、 `error`** 是具有響應性的 RefImpl ,需使用 .value 取值;而 refresh 和 clear 是函式
```jsx
const { data, status, error, refresh, clear } = await useAsyncData(key, () => $fetch(url))
// ref 取值
data.value; // 非同步請求回傳的資料
status.value; // 非同步請求的狀態
error.value; // 取得錯誤資訊
// 執行函式
refresh(); // 更新資料
clear(); // 重置資料狀態
```
- data:非同步函式在發出請求後回傳的資料。
- status : 資料請求的狀態,可以是 `idle`( 初始狀態,請求尚未開始 ) 、`pending` ( 資料請求已經發出,正在等待回應 ) 、`success` ( 請求成功完成並且已經取得資料 )、`error` ( 請求失敗)
- error:如果非同步失敗,回傳錯誤相關的物件資料。在請求成功時會回傳 null。
- clear: 清除所有回傳值資料的狀態。data 會被設定為 undefined , `error` 設定為 `null` ,status 被設定為 'idle' 。
- refresh:用於更新 data 資料的函式,會重新發送一次資料請求。
## useFetch
`useFetch` 語法是由 `useAsyncData` 和 `$fetch` 封裝而成,等同於下方的寫法 :
```jsx
await useAsyncData('', () => $fetch(url),{ /* 參數 */ },{ /* useAsyncData 提供的參數 */ });
```
因為 `useFetch` 有使用 `useAsyncData` 封裝,所以會產生 key 值。 key 值會根據 url 和參數自動產生,因此無法讀取和操作 key 值。`useFetch` 的運作也是在伺服器端發出請求並取得資料後,將資料狀態傳輸到客戶端。
```jsx
// useFetch 會依據 url 和參數自動產生一個唯一的 key,避免重複請求
await useFetch(url,{ /* 參數 */ });
```
### 使用方式
useFetch 帶入的參數皆繼承自 `$fetch` 與 `useAsyncData` 。除了沒有回傳的非同步函式以外,其他參數的使用方法與特性皆無差異,可以在第二個物件帶入繼承過來的參數,例如 :
```jsx
await useFetch(
url,
{
/* 來自 $fetch */
method:"POST",
query: { /*...*/ },
onRequest({ request, options }){ /*...*/ },
/* 來自 useAsyncData */
pick: [''],
transform: (response )=> { response }
}
)
```
以下將以 useAsyncData 的 transform 和 pick 範例為例,改寫成 useFetch 的寫法,同時使用 `useAsyncData` 和 `$fetch` 提供的參數 :
### transform
步驟一. 原本的 `() => $fetch(),` 移除,將 API 的網址移至第一個參數。
步驟二. 將 transform 函式移動至第二個參數。
```jsx
// 對應至範例的 /pages/useFetch.vue
const { data: userTransformData } = await useFetch(
"https://randomuser.me/api/",
{
transform: (response) => {
// 修改 response 的結果,取出並回傳物件結構
const [resultObject] = response.results;
console.log(resultObject);
return resultObject;
},
}
);
```
### pick
步驟一. 原本的 `() => $fetch(),` 移除,將 API 的網址移至第一個參數。
步驟二. 對應至範例的 /pages/useFetch.vue,將 `$fetch()` 帶入的 query 移到第二個參數物件。
```jsx
// 對應至範例的 /pages/useFetch.vue
const { data: userPickData } = await useFetch("https://randomuser.me/api/", {
query: {
seed: "be579ffd62ede3ce",
},
pick: ["info"],
});
```
### 回傳值
useFetch 的回傳值皆繼承自 `useAsyncData` ,用法與特性皆相同。
```jsx
const { data, status, error, refresh, clear } = await useFetch(url,{ /* 參數 */ });
// ref 取值
data.value; // 非同步請求回傳的資料
status.value; // 非同步請求的狀態
error.value; // 取得錯誤資訊
// 執行函式
refresh(); // 更新資料
```
---
## useFetch 跟 useAsyncData 差異
`useFetch` 適合在單次 API 請求中使用,通常用於處理較為簡單的邏輯,例如設置請求參數或處理響應資料。
而 `useAsyncData` 更加靈活,允許在第二個參數中使用非同步函式來處理更複雜的邏輯。例如使用 `Promise.all()` 同時處理多個 API 請求,或是在獲取 `$fetch` 的回應後自訂資料的格式。以下方的範例為例, `useAsyncData` 使用了 `Promise.all()` 同時發出兩個 API 請求,並在請求完成後取出 firstUser, secondUser 資料,透過 `.map()` 重組資料,從每筆資料中提取results[0] 的物件資料。
```html
<script setup>
const { data } = useAsyncData("getMutipleRandomuser", async () => {
const [firstUser, secondUser] = await Promise.all([
$fetch("https://randomuser.me/api/"),
$fetch("https://randomuser.me/api/"),
]);
/*
{
results": [
{"gender": "female", ... } <==== 只取出 results 陣列內的物件資料
],
"info": {
...
}
}
*/
return [firstUser, secondUser].map((userItem) => {
const [userInformation] = userItem.results;
return userInformation;
});
});
</script>
```
<br>
> 今日學習的[範例 Code - 資料夾: day10-nuxt3-usefetch-example](https://github.com/hexschool/nuxt-daily-tasks-2024)
## 題目
請接續 Day8 題目的練習或是 fork Day8 解答的 [模板](https://github.com/jasonlu0525/nuxt3-live-answer/tree/day8-dynamic-router),完成以下條件 :
- 將 `pages/room/index.vue` 取得房型列表以及 `pages/room/[id].vue` 取得取得房型詳細資料功能使用的 ES6 fetch() 修改成使用 Nuxt3 `useFetch()` 或是 `useAsyncData()` 在伺服器端取得資料。
- 在 `pages/room/index.vue` 的房型列表中,點擊房型後能夠進入房型內頁。
- 進入房型內頁後,透過動態路由的網址參數 [串接 API](https://nuxr3.zeabur.app/swagger/#/Rooms%20-%20%E6%88%BF%E5%9E%8B/get_api_v1_rooms__id_) 取得房型詳細資料。
> ❗ 需注意 Day8 題目使用的 fetch() 是瀏覽器提供的 Web API ,並非 Nuxt3 的 `$fetch` 和 `ofetch`。 兩者沒有任何關連性。
## 回報流程
將答案上傳至 GitHub 並複製 GitHub repo 連結貼至底下回報就算完成了喔 !
解答位置請參考下圖(需打開程式碼的部分觀看)

<!--
解答: https://github.com/jasonlu0525/nuxt3-live-answer/tree/day10-nuxt3-usefetch
-->
回報區
---
| # | Discord | Github / 答案 |
| --- | ----- | ----- |
|1|眼睛|[Github](https://github.com/Thrizzacode/nuxt3-live-question/tree/day8-dynamic-router)|
|2|dragon|[Github](https://github.com/peterlife0617/2024-nuxt-training-homework01/tree/feature/day10)|
|3|kevinhes|[Github](https://github.com/kevinhes/nuxt-daily-mission/tree/day10)|
| 4 | Steven | [GitHub](https://github.com/y7516552/nuxt3-live-question/tree/day8) |
| 5 | LinaChen | [GitHub](https://github.com/Lina-SHU/nuxt3-live-question) |
| 6 | MY | [GitHub](https://github.com/ahmomoz/nuxt3-live-question/tree/day10-nuxt3-useFetch-hw) |
| 7 | Rocky| [Github](https://github.com/WuRocky/Nuxt-Day10-useFetch-useAsyncData) |
| 8 | hsin yu | [Github](https://github.com/dogwantfly/nuxt3-daily-task-live-question/commit/e326ad201175bfdd78bdfa1db54e1500d76cba0d) |
| 9 | Jim Lin | [Github](https://github.com/junhoulin/Nuxt3-hw-day10) |
| 10 | NeiL | [Github](https://github.com/Neil10241126/nuxt-demo/tree/day10-nuxt3-useFetch) |
| 11 | Stan | [Github](https://github.com/haoxiang16/nuxt3-live-question/tree/day10-nuxt3-usefetch) |
| 12 | Fabio20 | [Github](https://github.com/fabio7621/nuxt3-live-question-d2/tree/nuxt-live-day10) |
| 13 | Ariel | [Github](https://github.com/Ariel0508/nuxtday6/tree/nuxtday10) |
| 14 | Tough life | [GitHub](https://github.com/hakuei0115/Nuxt_testing) |
| 15 | wei_Rio | [GitHub](https://github.com/wei-1539/nuxtDaily10) |
| 16 | runweiting | [Github](https://github.com/runweiting/2024-NUXT-Task/tree/day9) |
| 17 | 阿塔 | [GitHub](https://github.com/QuantumParrot/2024-Hexschool-Nuxt-Camp-Daily-Task) |
| 18 | tanuki狸 | [Github](https://github.com/tanukili/Nuxt-2024-week01-2/tree/day8-dynamic-router) |
| 19 | Nielsen | [Github](https://github.com/asz8621/nuxt3-daily-task/tree/day10-nuxt3-usefetch) |
| 20 | barry1104 | [Github](https://github.com/barrychen1104/nuxt3-live-question/tree/day8-dynamic-router) |
|21|好了啦|[GitHub](https://github.com/ZhangZJ0906/nuxt-live)|
|22|Johnson|[GitHub](https://github.com/tttom3669/2024_hex_nuxt_daily/tree/day10-useFetch-useAsyncData)|
<!-- 快速複製
| --- | --- | [Github]() |
-->