Vue3 Composition API 核心 – setup() 函式完整教學
簡介
在 Vue 3 中,setup() 是 Composition API 的入口點,也是所有組件邏輯的起點。它取代了 Vue 2 時期的 data()、computed、methods、watch 等選項,提供更彈性的程式碼組織方式。對於想要打造可重用、易維護的大型專案,掌握 setup() 的使用方法是必不可少的。
本篇文章將從概念說明、實作範例、常見陷阱到最佳實踐,逐步帶你了解 setup() 的每一個細節,讓你能在 日常開發 中快速上手、寫出更乾淨的程式碼。
核心概念
1. setup() 的執行時機
- 在 component instance 被建立之前 呼叫。此時還沒有
this,只能透過參數取得props、context。 - 只會執行 一次,即使組件重新渲染也不會再次呼叫。
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 | 包含 attrs、slots、emit 三個屬性 |
setup(props, { emit, attrs, slots }) {
// attrs 包含未被宣告的 HTML attribute
// slots 可直接取得插槽內容
}
3. 返回值
setup() 可以返回以下任意組合,Vue 會自動將它們「掛載」到模板中:
- 響應式資料:
ref、reactive、computed - 函式(methods)
- 生命週期鉤子:
onMounted、onUpdated、onUnmounted等 - 其他自訂物件(例如 API 服務)
setup() {
const count = ref(0)
const double = computed(() => count.value * 2)
function inc() {
count.value++
}
return { count, double, inc } // 這三個會在模板中直接使用
}
4. ref 與 reactive 的差異
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 default,setup()內容會自動被包裝。
範例 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> 中使用。 |
最佳實踐
- 盡量使用
<script setup>:語法更簡潔,省去export default與return。 - 把可重用邏輯抽成 composable:如
useFetch、useToggle,提升程式碼可讀性與測試性。 - 保持
setup()簡潔:只放置資料、方法與鉤子,複雜邏輯交給外部函式或 composable。 - 使用 TypeScript 時:給
props、emit加上型別,讓 IDE 能提供完整的自動完成與錯誤提示。 - 適時使用
watch或watchEffect:監聽特定值變化時才執行副作用,避免不必要的重新渲染。
實際應用場景
| 場景 | 為何使用 setup() |
範例概念 |
|---|---|---|
| 表單驗證 | 可將驗證規則抽成 composable,並在 setup() 中回傳 errors、validate。 |
useFormValidator |
| 資料表格分頁 | 透過 ref 保存分頁資訊,watchEffect 監聽頁碼變化自動抓取資料。 |
usePaginatedTable |
| 全域主題切換 | 使用 provide/inject 搭配 reactive 主題物件,在根組件的 setup() 中提供。 |
ThemeProvider |
| WebSocket 即時訊息 | 在 onMounted 建立連線,onUnmounted 關閉,並使用 ref 保存訊息陣列。 |
useWebSocket |
| 動畫與過渡 | 結合 ref 控制狀態,搭配 watch 或 watchEffect 觸發 CSS class 變化。 |
useFadeTransition |
這些場景都能藉由 Composition API 的模組化特性,使程式碼更易維護、測試與重用。
總結
setup()是 Vue 3 Composition API 的核心入口,負責建立組件的響應式狀態、方法、生命週期與事件。- 了解
props、context以及ref、reactive的差異,能避免常見的只讀與解構陷阱。 - 透過 Composable(自訂 Hook)把邏輯抽離,讓程式碼更具可讀性與可測試性。
- 使用
<script setup>可以讓setup()寫法更簡潔,同時保持所有功能不變。 - 在實務開發中,將
setup()與最佳實踐結合,能有效提升專案的可維護性與開發效率。
掌握了 setup(),你就掌握了 Vue 3 最強大的組件開發方式。未來無論是小型原型還是大型企業級應用,都能得心應手地運用 Composition API,寫出乾淨、可擴充的程式碼。祝你開發順利,玩得開心! 🎉