---
# System prepended metadata

title: "\U0001F3C5 Day 10 - useFetch 與 useAsyncData"
tags: [2024 Nuxt 直播班]

---

# 🏅 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]() |
-->
