# 🏅 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://i.imgur.com/vftL5i0.png) <!-- 解答: 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]() | -->