# 🏅 Day 9 - $fetch 與 ofetch
## 今日學習目標
- 理解 Nuxt3 基於 ofetch 套件提供了 $fetch 方法
- 學習在 Nuxt3 中使用 $fetch 進行 API 請求
- 了解 $fetch 使用的注意事項
## 前言
在 Day8 的問題中,我們使用了 Web API Fetch 來取得 API 資料並渲染到畫面上。以下是使用原生 `fetch` 的作法 :
```jsx
const roomsList = ref([]);
const apiUrl = "https://nuxr3.zeabur.app/api/v1/rooms";
// ↓ Web API 原生的 fetch
fetch(apiUrl)
.then((response) => {
if (!response.ok) {
throw new Error("取得房型資料失敗");
}
return response.json();
})
.then((data) => {
const { result } = data;
roomsList.value = result;
})
.catch((error) => {
console.error("發生錯誤:", error);
});
```
而在 Nuxt3 整合了 `ofetch` 套件,提供了全域的 `$fetch` 方法,讓我們可以在伺服器和客戶端進行非同步操作。
## $fetch 的使用方式
Nuxt3 使用 [ofetch](https://github.com/unjs/ofetch) 建立全域的 $fetch 方法,並透過自動匯入 (Auto Imports) 的方式在每個 Vue 元件中可直接使用。因此,我們可以使用 ofetch 定義的屬性與方法格式來發出 API 請求 。
### $fetch 帶入參數
`$fetch` 方法可以接收多種參數來配置請求。以下針對經常使用的參數進行補充:
```jsx
await $fetch(url, {
method: "",
body: {},
baseURL: "",
query: {},
params: {}
headers: {},
})
```
- url : API 請求的 URL。
- method : HTTP 請求的方法,例如 `GET`、`POST`、`PUT`、`PATCH`、`DELETE` 。
- body : 加入 HTTP 請求 body 的物件。ofetch 會自動將物件轉換為 `application/json`格式。
- baseURL : 設定基本的 URL 路徑。在發送請求時,`baseURL` 會自動與 `url` 參數結合。
- query: 查詢參數,以 `?` 的形式附加在 URL 的末尾,例如 `https://www.example.com?key=value`。
- params: 在 ofetch 中 params 是 query 的別名,用法與 query 相同。
- headers: 設定請求的 HTTP 標頭,例如 `Content-Type` 或 `Authorization`。
### **$fetch 帶入攔截器函式 (interceptors)**
除了帶入參數以外,`$fetch` 還提供了攔截器功能,在請求發出前或收到回應後進行一些處理操作,以下是攔截器的用途:
- **onRequest**: : 在發送請求之前執行,用於修改請求的設定,例如添加或修改請求的標頭 ( headers ) 。
- **onRequestError**: 在請求發生錯誤時執行。可以來處理請求錯誤,例如發出錯誤提示訊息。
- **onResponse**: 在接收到回應後執行,可以用來處理回傳的資料。
- **onResponseError**: 當回應發生錯誤時執行,可以用來處理回應錯誤。例如處理伺服器回應的 HTTP 401 狀態碼,並將使用者重新導向至登入頁面。
```jsx
await $fetch("url", {
...其他的參數
onRequest({ request, options }) {
// 舉例 : 設置 request headers
options.headers = options.headers || {};
options.headers.authorization = `Bearer token XXX`;
},
onRequestError({ request, options, error }) {
// 處理 request 錯誤
},
onResponse({ request, response, options }) {
// 處理回傳資料
return response._data;
},
onResponseError({ request, response, options }) {
// 處理 response 發生的錯誤
// 舉例:處理伺服器 HTTP 401 status
if (response && response.status === 401) {
console.error("未授權的訪問,請重新登入");
router.replace('/login');
}
},
});
```
## $fetch 使用的注意事項
在頁面加載或元件初始化時使用 `$fetch` 進行資料請求(GET)需要注意**重複發送請求**的問題。$fetch 會在伺服器端發送一次請求以進行伺服器端渲染(SSR),然後將請求的回應傳輸到客戶端。當客戶端收到回應後,會再發送一次相同的請求。
### 重複發送請求的問題
以下範例展示了在 `/page/index.vue` 使用 `$fetch` 請求 [randomuser API](https://randomuser.me/) 的情境:
```html
<!-- /page/index.vue -->
<script setup>
const data = await $fetch("https://randomuser.me/api/");
const [result] = data.results;
</script>
<template>
<img
:src="result.picture?.large"
:alt="`${result.name.first} ${result.name?.last}`"
/>
<h1>{{ result.name?.first }} {{ result.name?.last }}</h1>
<h3>Email: {{ result.email }}</h3>
</template>
```
執行後會在伺服器端和客戶端都發送一次請求,因此渲染後在瀏覽器 console 會發生 `Hydration text content mismatch` 警告與 `Hydration completed but contains mismatches.` 錯誤 ( 如下圖 ) 。

這個錯誤表示 `{{ data.results }}` 在伺服器端渲染與客戶端渲染的結果不一致。因為在伺服器端,`$fetch` 會發送一次請求來獲取數據並進行 SSR,然後將結果發送到客戶端,而客戶端在接收到 HTML 後,會再次發送一個相同的 `$fetch` 請求。由於 `randomuser` API 每次回應的資料都是隨機的,因此伺服器端與客戶端各自請求一次 API 後,會得到不同的資料,造成渲染不一致的問題。
### 避免重複發送請求的方法
為了避免伺服器端與客戶端各請求一次 API,同時造成潛在的渲染問題。直接在頁面或是元件取得資料時建議改成使用 [useFetch](https://nuxt.com.cn/docs/api/composables/use-fetch) 或是 [useAsyncData](https://nuxt.com.cn/docs/api/composables/use-async-data) 搭配 **`$fetch`** 來防止重複發送請求。
```html
<!-- /page/index.vue -->
<script setup>
// 改法一.用 useFetch
const { data } = await useFetch("https://randomuser.me/api/");
const [result] = data.value.results;
// 改法二.用 useFetch 搭配 $fetch()
// const { data } = await useAsyncData("getRandomuser", () =>
// $fetch("https://randomuser.me/api/")
// );
// const [result] = data.value.results;
</script>
<template>
<img
:src="result.picture?.large"
:alt="`${result.name.first} ${result.name?.last}`"
/>
<h1>{{ result.name?.first }} {{ result.name?.last }}</h1>
<h3>Email: {{ result.email }}</h3>
</template>
```
以上關於 `$fetch` 重複發送請求的特性可以閱讀 [官方文件](https://nuxt.com/docs/api/utils/dollarfetch) 的說明 。而 [useFetch](https://nuxt.com.cn/docs/api/composables/use-fetch) 與 [useAsyncData](https://nuxt.com.cn/docs/api/composables/use-async-data) 會在 Day10 進行介紹
### 適合使用 $fetch 的情境
如果確定請求是在客戶端由操作者在操作時觸發,例如透過點擊事件送出請求,不是在頁面或是元件初始化時取得資料就可以使用 `$fetch`。例如下方範例,在按鈕點擊事件中使用 `$fetch`就不會發送兩次請求,導致渲染問題 :
```html
<!-- /page/index.vue -->
<script setup>
const userData = ref({});
const getUserData = async () => {
const response = await $fetch("https://randomuser.me/api/");
userData.value = response.results[0];
};
</script>
<template>
<template v-if="userData.name">
<img
:src="userData.picture?.large"
:alt="`${userData.name?.first} ${userData.name?.last}`"
/>
<h1>{{ userData.name?.first }} {{ userData.name?.last }}</h1>
<h3>Email: {{ userData.email }}</h3>
</template>
<button type="button" @click="getUserData">取得 User 資料</button>
</template>
```
<br>
> 今日學習的[範例 Code - 資料夾: day9-nuxt3-fetch-example](https://github.com/hexschool/nuxt-daily-tasks-2024)
## 題目
請 fork 這一份 [模板](https://github.com/jasonlu0525/nuxt3-live-question/tree/day9-nuxt3-fetch),實作帳號註冊功能 :
- 在 `/pages/register.vue` 使用模板提供的操作介面填寫註冊表單。點擊 “註冊” 按鈕後使用 Nuxt3 提供的方法串接旅館的 [註冊 API](https://nuxr3.zeabur.app/swagger/#/Users%20-%20%E4%BD%BF%E7%94%A8%E8%80%85/post_api_v1_user_signup) ,將請求送出。
- 需使用 try catch 處理請求成功與失敗的訊息,請求成功與失敗皆使用 [sweetAlert2 套件](https://sweetalert2.github.io/) 顯示訊息。sweetAlert2 套件在模板已有安裝與引入,不需再額外設定。
```jsx
$swal.fire({
position: "center",
icon: ... ,
title: ... ,
showConfirmButton: false,
timer: 1500,
});
```
- 表單不需處理表單驗證、身分驗證、檢查登入狀態以及存入 cookie。
- [註冊 API](https://nuxr3.zeabur.app/swagger/#/Users%20-%20%E4%BD%BF%E7%94%A8%E8%80%85/post_api_v1_user_signup) 夾帶的請求體(Request Body)格式,需要注意以下地方 :
- 所有欄位都必填。
- 密碼需要至少 8 碼以上,並英數混合。
- 電話格式可以是手機號碼與市內電話。
- birthday 格式可以是 "yyyy-mm-dd”。
- zipcode 需要對照到各縣市各區的郵遞區號,可以參考 [郵遞區號速查一覽表](https://c2e.ezbox.idv.tw/zipcode.php)。
- 串接 API 時需避免在伺服器端與客戶端重複發送請求。

## 回報流程
將答案上傳至 GitHub 並複製 GitHub repo 連結貼至底下回報就算完成了喔 !
解答位置請參考下圖(需打開程式碼的部分觀看)

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