---
url: /blog-vue/do-not-always-fetch-api-in-on-mounted.md
description: Vue 你還在 onMounted 中取 API 嗎？其實不一定要這樣做，來看看不同做法吧。( ͡~ ͜ʖ ͡°)
---

![do-not-always-fetch-api-in-on-mounted](/do-not-always-fetch-api-in-on-mounted.webp){.cover}

# 其實 Vue 不一定要在 onMounted 取 API

大家好，我是鱈魚。(｡･∀･)ﾉﾞ

最近看到大家討論取得 API 時機，有許多人都提到「一定要」或者「習慣」，在 `onMounted` 這個 hook 內呼叫 API 取得資料。

其實這也沒不好，但是也沒什麼好處就是了。(。・ω・。)

讓我們來思考一下下，有關於 Vue 取得 API 資料這檔事。（~~不會轉生，也沒有史萊姆~~）

## 「一定要」在 onMounted

有人說「一定要」是因為不這麼做會壞掉，這個可以從 2 個部分來看。

首先是 `onMounted` 這個 hook 是甚麼？讓我們看看官網的定義：

> Registers a callback to be called after the component has been mounted.

意思就是「註冊一個會在元件安裝完成後被呼叫的 callback」。

甚麼意思呢？也就是他和有沒有資料一點關係都沒有。`onMounted` 沒辦法幫你保證有資料、template 不會壞，從此天下太平，再也不用加班，。(´。＿。｀)

所以就牽扯到了第二部分：為甚麼會壞掉？

馬上取會壞，`onMounted` 取就不會壞，這表示你的資料可能和 DOM 或元件發生耦合，這其實不是好現象，不但違反了資料驅動的概念還埋了隱形的~~翅膀~~…地雷。

具體原因可能如下：

* API 參數耦合了某個 DOM 的資料，導致「沒有在 `onMounted` 取值」等於「對 undefined 動手動腳」，當然就壞掉惹。（〃｀ 3′〃）
* API 參數或操作耦合了某個子元件，而 Vue 的 `onMounted` 會保證子元件都 `onMounted` 完後，自己的 `onMounted` 才會觸發，導致 `onMounted` 取才沒事。

所以總結來說，所謂的「壞掉」很有可能只是資料流沒考慮 undefined 的情境。

::: tip
可能啦，我巫力不足，只能通靈到這裡惹。 ╮(╯▽╰)╭
:::

如果有使用 TypeScript 就很少會出現這類問題了，因為 TypeScript 會先不開心、滿江紅給你看。ᕦ(ò\_óˇ)ᕤ

## 「習慣」在 onMounted

習慣上可能會這樣寫：

```vue
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { getUser, User } from './api'

const user = ref<User>()

onMounted(async () => {
  try {
    user.value = await getUser()
  }
  catch (error) {
    console.error('QQ')
  }
})
</script>

<template>
  <h1>Classic</h1>
  <div>
    user: {{ user?.name }}
  </div>
</template>
```

考慮讀取狀態則可能再加個 loading：

```vue
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { getUser, User } from './api'

const user = ref<User>()
const isLoading = ref(false)

onMounted(async () => {
  try {
    isLoading.value = true
    user.value = await getUser()
  }
  catch (error) {
    console.error('QQ')
  }
  finally {
    isLoading.value = false
  }
})
</script>

<template>
  <h1>Classic</h1>
  <div v-if="isLoading">
    資料讀取中...(～﹃～)~zZ
  </div>
  <div v-else>
    user: {{ user?.name }}
  </div>
</template>
```

剛剛說了，其實不用在 `onMounted` 才取資料，早一點取也行，所以可以變成這樣：

```vue
<script setup lang="ts">
import { ref } from 'vue'
import { getUser, User } from './api'

const user = ref<User>()
const isLoading = ref(true)

getUser().then((data) => {
  user.value = data
}).catch(() => {
  console.error('QQ')
}).finally(() => {
  isLoading.value = false
})
</script>
...
```

路人：「這樣真的有比較好嗎？」

鱈魚：「當然沒有。(≧∇≦)ﾉ」

路人：（抄起球棒）

鱈魚：「冷靜冷靜，還沒說完啊。(っ °Д °;)っ」

## 來點 Composition

讓我們回想一下 Vue 3 的 Composition API 精神，一但要取得資料變多後，仔細觀察可以發現「資料、狀態（isLoading）、取資料的 function」會重複出現。

讓我們抽離、重組一下，新增一個檔案。

`useAsyncData.ts`

```ts
import { ref } from 'vue'

interface UseAsyncDataParam<Data> {
  executor: () => Promise<Data>;
  onError?: (error: unknown) => void;
}

export function useAsyncData<Data>(param: UseAsyncDataParam<Data>) {
  const data = ref<Data>()
  const isLoading = ref(true)

  param.executor().then((result) => {
    data.value = result
  }).catch((error) => {
    param.onError?.(error)
  }).finally(() => {
    isLoading.value = false
  })

  return {
    data,
    isLoading,
    execute: param.executor,
  }
}
```

::: tip
寫過 Nuxt 的朋友們一定會有熟悉的感覺。(. ❛ ᴗ ❛.)
:::

現在讓我們用這個 `useAsyncData` 改寫一下剛剛的例子看看。

```vue
<script setup lang="ts">
import { getUser } from './api'
import { useAsyncData } from './useAsyncData'

const {
  data: user,
  isLoading,
} = useAsyncData({
  executor: () => getUser(),
  onError() {
    console.error('QQ')
  },
})
</script>

<template>
  <h1>Composable</h1>
  <div v-if="isLoading">
    資料讀取中...(～﹃～)~zZ
  </div>
  <div v-else>
    user: {{ user?.name }}
  </div>
</template>
```

就算有多個狀態要取得也變得更好組織程式碼了，是不是看起來乾淨許多呢？

恭喜我們重複造了 Nuxt 的 `useAsyncData` 與 VueUse 的 `useAsyncState` 的輪子了！

✧⁑｡٩(ˊᗜˋ\*)و✧⁕｡

沒錯，雖然目前這個 `useAsyncData` 的功能很簡陋，有很多需要改進的部分，但是這就是上述兩個 API 的基礎精神。

不過 Nuxt 還包含了 SSR 行為，所以更加複雜，推薦大家可以看看 VueUse `useAsyncState` 的[文件](https://vueuse.org/core/useAsyncState/#useasyncstate)或[原始碼](https://github.com/vueuse/vueuse/blob/main/packages/core/useAsyncState/index.ts)，一定可以獲益良多。

文章到此結束了，感謝您的閱讀，以上範例程式碼可以[在此取得](https://play.vuejs.org/#eNrNVb1u1EAQfpWVm/NJhy8oVJdLohBSgPhJIDTIzeKbS5z4dq3d9eXCyQiJgpIuEg1vAA0NFLwNoHR5BWbW6z378gMVoohjz8w3O/N9M3vzYCvPo2kBwSAY6kSluWEaTJFvxCKd5FIZtp1xrdNkm2tgYyUnrBP1GzYCd9YW0RL/a/4ygzagZa4xw351JB6GHwYmecYN4Bdjw+axfWdq5ybrsN9ABb3A6ESKcXoQHWkpsKc5AeMgQWSagXqSm1QKHQcDZj3k41kmTx5Ym1EF9Gp7cgjJ8RX2Iz0jWxzsKtCgphAH3me4OgBTuXeePYYZvnvnRI6KDKNvcD4FLbOCaqzC7hZihGU34my19y3XqTjY1zszA0LXTVGhFFna+DhAnom061pflLsa3bG4WJTI4pK8y7PBMi4O1hGO2RZzMmdSPJKFMDDqMQVjVjr12wMyZ3jkcySux+jpo6I+z9MODQIqqA0ryLlOiYYUtxF2MUflSvVDyUfYfuUPqWvyxsKlDruROQQRhiNueJetb1QtU8poyrMCEEguxJTdKOEmOQzDRRydIjOIQCmpws7eXoeyY+Q4Fcj/aTPW1+ITj3mmwcb/YcIPb284ood9fLe2UTpl01vpGNn1mS3JJNj5l3e/zj6cf37z8/3Zj6+foigKL75/vPj2Fp/d169e2Ax9TNHMBViNw1P/qP7cvmxGgk+AlSj5ArW8TihJZDTqDzMrXorqqjFPoNJuHitCU6IB00ZhsWtkKulBfw42LkRC01dLj/w5qMJ5UoIJOGG7OAapBqd2iMslsyk4pm0wo/HbTycgC+M1cB5KZQFhw1SX1knkqLMwkzLuo+yx1ZWVldrgPLQDyNGWPhXJPZyTigM/wJeH27Xbosejd7nikyG9+XphBklhJKpRtVH3boNcLVLs0PxtYowdxAErxLGQJ8IipjId3cx1s4EqcZhTJYPrivOiVFtGC+IWsELXJN24hK4YxuxZUd2o30hUqchMWzs6ya9PFeD1qLfTUtCGVSfUNEUuZIFc3lYHu3ZjmxPQms5Wob1Lebylahbnrd2781uh7O166bfwn16wPqw5II2oprlxH9vbjgjAOaTkrcsPv0oksgnFPbxi0P0FYPFOO3sdWAavvnmr37O/uk89tf/plVr+BtH4PtM=)。

有其他想法請不吝告訴我，鱈魚感謝您！(o゜▽゜)o☆

## 總結 🐟

* 不一定要在 `onMounted` 取 API，這樣做可能會讓程式碼更複雜
* 使用 Composition API 可以更好組織程式碼
* 重複造輪子是進步的關鍵，但是也不要隨意在正式專案造輪子，以免埋下地雷
