本文 AI 產出,尚未審核

Vue3 Composition API 核心 – setup() 函式完整教學


簡介

在 Vue 3 中,setup()Composition API 的入口點,也是所有組件邏輯的起點。它取代了 Vue 2 時期的 data()computedmethodswatch 等選項,提供更彈性的程式碼組織方式。對於想要打造可重用、易維護的大型專案,掌握 setup() 的使用方法是必不可少的。

本篇文章將從概念說明、實作範例、常見陷阱到最佳實踐,逐步帶你了解 setup() 的每一個細節,讓你能在 日常開發 中快速上手、寫出更乾淨的程式碼。


核心概念

1. setup() 的執行時機

  • 在 component instance 被建立之前 呼叫。此時還沒有 this,只能透過參數取得 propscontext
  • 只會執行 一次,即使組件重新渲染也不會再次呼叫。
export default {
  // Vue 3 的 Options API 仍可與 Composition API 共存
  props: { msg: String },
  setup(props, { emit, slots }) {
    console.log('setup 被呼叫', props.msg)   // 只會印出一次
    // 這裡不能使用 this
  }
}

2. 參數說明

參數 類型 作用
props readonly object 只讀的 props,若要解構需使用 toRefs()
context object 包含 attrsslotsemit 三個屬性
setup(props, { emit, attrs, slots }) {
  // attrs 包含未被宣告的 HTML attribute
  // slots 可直接取得插槽內容
}

3. 返回值

setup() 可以返回以下任意組合,Vue 會自動將它們「掛載」到模板中:

  • 響應式資料refreactivecomputed
  • 函式(methods)
  • 生命週期鉤子onMountedonUpdatedonUnmounted
  • 其他自訂物件(例如 API 服務)
setup() {
  const count = ref(0)
  const double = computed(() => count.value * 2)

  function inc() {
    count.value++
  }

  return { count, double, inc }   // 這三個會在模板中直接使用
}

4. refreactive 的差異

ref reactive
適用對象 基本類型(數字、字串、布林)或單一值 物件或陣列
取得值 .value 直接存取屬性
解構後保持響應 需要 toRefs() 自動保持響應(但解構會失去響應)
const num = ref(10)          // num.value === 10
const obj = reactive({ a: 1 }) // obj.a === 1

5. toRefs()toRaw()

  • toRefs():將 reactive 物件的每個屬性轉成 ref,方便解構同時保留響應式。
  • toRaw():取得未被 Proxy 包裝的原始物件(少用於除錯或第三方庫需要原始資料時)。
setup(props) {
  const state = reactive({ x: 0, y: 0 })
  const { x, y } = toRefs(state)   // x、y 為 ref,解構後仍具備響應式
  return { x, y }
}

程式碼範例

以下示範 5 個常見且實用的 setup() 用法,從最基礎的響應式資料到自訂 Hook,幫助你快速上手。

範例 1️⃣ 基本計數器

<template>
  <button @click="inc">點擊 +1</button>
  <p>目前計數:{{ count }}</p>
  <p>雙倍值:{{ double }}</p>
</template>

<script setup>
import { ref, computed } from 'vue'

const count = ref(0)                     // 基本的響應式資料
const double = computed(() => count.value * 2) // 計算屬性

function inc() {
  count.value++                         // 更新值必須使用 .value
}
</script>

重點:在 <script setup> 中不需要 export defaultsetup() 內容會自動被包裝。


範例 2️⃣ Props 只讀與解構

<template>
  <h2>{{ title }}</h2>
  <p>作者:{{ author }}</p>
</template>

<script setup>
import { toRefs } from 'vue'

defineProps({
  title: String,
  author: String
})

// 直接解構會失去響應式,使用 toRefs 保持
const { title, author } = toRefs(defineProps())
</script>

技巧defineProps() 只能在 <script setup> 中使用;若要在普通 setup() 中取得 props,請使用 setup(props)


範例 3️⃣ 使用生命週期鉤子與 API 呼叫

<template>
  <ul>
    <li v-for="item in posts" :key="item.id">{{ item.title }}</li>
  </ul>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'

const posts = ref([])

onMounted(async () => {
  const { data } = await axios.get('https://jsonplaceholder.typicode.com/posts')
  posts.value = data.slice(0, 5)   // 只取前 5 筆
})
</script>

說明onMounted 只會在組件掛載完畢後執行一次,適合放置 資料抓取 的程式碼。


範例 4️⃣ 自訂 Hook:useToggle

// src/composables/useToggle.js
import { ref } from 'vue'

export function useToggle(initial = false) {
  const state = ref(initial)
  const toggle = () => { state.value = !state.value }
  return { state, toggle }
}
<template>
  <button @click="toggle">切換:{{ state }}</button>
</template>

<script setup>
import { useToggle } from '@/composables/useToggle'

const { state, toggle } = useToggle(true)
</script>

好處:將可重用的邏輯抽離成 Composable,讓其他組件只要 import 即可使用。


範例 5️⃣ 事件發射 (emit) 與插槽 (slots)

<template>
  <button @click="handleClick">發射自訂事件</button>
  <slot name="default">預設內容</slot>
</template>

<script setup>
import { defineEmits, defineSlots } from 'vue'

const emit = defineEmits(['custom'])
const slots = defineSlots()

function handleClick() {
  emit('custom', { msg: 'Hello from child' })
}
</script>
<!-- 父層使用 -->
<Child @custom="onCustom">
  <template #default>
    <p>這是插槽內容</p>
  </template>
</Child>

要點emit 只能在 setup()(或 <script setup>)內取得,slots 允許在組件內部直接渲染傳入的插槽。


常見陷阱與最佳實踐

陷阱 說明 解決方式
直接解構 props 會失去 只讀響應式,導致 UI 不更新。 使用 toRefs(props) 或在模板中直接使用 props.xxx
忘記 .value ref 必須透過 .value 讀寫,否則會得到 RefImpl 物件。 <template> 中自動解包,JS 中務必加 .value
setup() 內使用 this setup() 沒有 this,會拋出錯誤。 改用返回的屬性或 context 取得所需資訊。
過度使用 reactive 包裝大型陣列 會產生不必要的 Proxy,影響效能。 只對需要深層監控的物件使用 reactive,陣列可用 ref([])
生命週期鉤子寫在外層 若在 setup() 外部直接呼叫 onMounted,會失去組件上下文。 確保所有鉤子都在 setup() 內部或 <script setup> 中使用。

最佳實踐

  1. 盡量使用 <script setup>:語法更簡潔,省去 export defaultreturn
  2. 把可重用邏輯抽成 composable:如 useFetchuseToggle,提升程式碼可讀性與測試性。
  3. 保持 setup() 簡潔:只放置資料、方法與鉤子,複雜邏輯交給外部函式或 composable。
  4. 使用 TypeScript 時:給 propsemit 加上型別,讓 IDE 能提供完整的自動完成與錯誤提示。
  5. 適時使用 watchwatchEffect:監聽特定值變化時才執行副作用,避免不必要的重新渲染。

實際應用場景

場景 為何使用 setup() 範例概念
表單驗證 可將驗證規則抽成 composable,並在 setup() 中回傳 errorsvalidate useFormValidator
資料表格分頁 透過 ref 保存分頁資訊,watchEffect 監聽頁碼變化自動抓取資料。 usePaginatedTable
全域主題切換 使用 provide/inject 搭配 reactive 主題物件,在根組件的 setup() 中提供。 ThemeProvider
WebSocket 即時訊息 onMounted 建立連線,onUnmounted 關閉,並使用 ref 保存訊息陣列。 useWebSocket
動畫與過渡 結合 ref 控制狀態,搭配 watchwatchEffect 觸發 CSS class 變化。 useFadeTransition

這些場景都能藉由 Composition API 的模組化特性,使程式碼更易維護、測試與重用。


總結

  • setup() 是 Vue 3 Composition API 的核心入口,負責建立組件的響應式狀態方法生命週期事件
  • 了解 propscontext 以及 refreactive 的差異,能避免常見的只讀與解構陷阱
  • 透過 Composable(自訂 Hook)把邏輯抽離,讓程式碼更具可讀性與可測試性。
  • 使用 <script setup> 可以讓 setup() 寫法更簡潔,同時保持所有功能不變。
  • 在實務開發中,將 setup() 與最佳實踐結合,能有效提升專案的可維護性開發效率

掌握了 setup(),你就掌握了 Vue 3 最強大的組件開發方式。未來無論是小型原型還是大型企業級應用,都能得心應手地運用 Composition API,寫出乾淨、可擴充的程式碼。祝你開發順利,玩得開心! 🎉