Composition API 是一組以「函式 + reactive
原語」為核心的 API,它透過 函式 + reactive
原語 的方式,讓使用者用更彈性、可抽換的方式來組合狀態與邏輯,取代過去 Options API(data
/ methods
/ computed
…)「依功能分散」的寫法。
基礎架構建立在物件的語法,所以邏輯分散在 data
、methods
、computed
、mounted
等不同區塊,可能導致元件變得難以管理,維護困難。
const app = Vue.createApp({
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++;
}
},
mounted() {
console.log("Component mounted.");
}
});
Composition API 是 Vue 3 引入的功能,讓開發者能夠更靈活地組織和管理邏輯。
它的核心組件包括 setup()
、reactive()
、ref()
、computed()
、watch()
和生命周期方法如 onMounted
。此外,還有 provide
/ inject
語法來共享狀態。開發者可以使用 composables
提取邏輯復用。
基於函式的語法,可以使用 setup
函式整合所需的邏輯,提升程式碼的可重用性和組織性,更加靈活,尤其適合大型專案。
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const count = ref<number>(0)
const increment = () => count.value++
onMounted(() => {
console.log("Component mounted.")
})
</script>
<template>
<button @click="increment">count: {{ count }}</button>
</template>
Composition API 和 Options API 主要的區別在於其 組織邏輯 的方式。Composition API 把功能邏輯封裝在函式中,也就是 composable
,例如 useFetch
,而 Options API 則是將邏輯分散在 data
、methods
等選項中。
使用 Composition API 可提升代碼的可維護性,特別是當應用程式規模擴大的時候,但是它可能對剛接觸 Vue 的開發者來說較為複雜。
最佳實踐:
useXxx
類命名的 composables
,避免複雜的解構賦值問題。script setup
中利用 macro 來提升效率,並運用 TypeScript 增加程式碼嚴謹度,接下來會直接做詳細介紹。setup
函式的功能setup
函式是 Composition API 的核心,以下是其主要功能:
ref
和 reactive
建立響應式資料。computed
定義基於響應式狀態的計算屬性。setup
中使用生命週期鉤子,如 onMounted
、onUnmounted
等。<script setup>
不需要 return,變數直接可用於 template。以下是範例是一個計數器,從上而下逐一說明前面所描述的 4 個功能:
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const count = ref<number>(0)
const message = ref<string>('Hello, Vue 3.5!')
const increment = () => count.value++
const reset = () => (count.value = 0)
onMounted(() => {
console.log("Component mounted. Current count:", count.value)
})
</script>
<template>
<p>{{ message }}</p>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
<button @click="reset">Reset</button>
</template>
Reactive
原語與生命週期)setup()
與 <script setup>
setup(props, context)
是元件在建立前執行的入口,回傳的東西會暴露給 template。<script setup>
:更精簡、較佳的 TS 推斷、支援編譯期巨集。<!-- Counter.vue -->
<script setup lang="ts">
import { ref, computed } from 'vue'
const count = ref(0) // 單一原始值/物件的響應式包裝
const double = computed(() => count.value * 2)
function inc() {
count.value++
}
</script>
<template>
<button @click="inc">count: {{ count }} / double: {{ double }}</button>
</template>
ref
vs reactive
ref(value)
:包裝單一值(number / string / boolean / Date / Object 都可),以 .value
取用;在 template 會自動解包。reactive(object)
:把物件/陣列轉為深層響應式 代理,解構時會失去響應,後續介紹響應式的時候會再細說這個坑。補充常用變體:
toRef(obj, key)
/ toRefs(obj)
:把 reactive
物件的屬性轉成 ref
,避免解構失去響應。shallowRef
/ shallowReactive
:只有最外層響應。markRaw(obj)
:標記成非響應,常用於大型第三方物件。computed(getter)
:快取的衍生值,依賴變更才重新計算。watch(source, (newVal, oldVal) => { … }, options)
:精準監聽某個/某些來源(ref
、getter
、或陣列來源)。watchEffect(() => { … })
:自動收集依賴的副作用,聲明式、快速,但不可拿到 oldVal。常見 watch
選項:
{ immediate: true }
初次就跑{ deep: true }
深層監聽物件{ flush: 'post' }
在 DOM 更新後觸發(避免閃爍/量測時機)在 setup()
內用對應的 hook:
onMounted
、onUpdated
、onUnmounted
onBeforeMount
、onBeforeUpdate
、onBeforeUnmount
onActivated
/ onDeactivated
(keep-alive 場景)<script setup lang="ts">
import { ref, onMounted } from 'vue'
const inputEl = ref<HTMLInputElement | null>(null)
onMounted(() => {
inputEl.value?.focus()
})
</script>
<template>
<input ref="inputEl" />
</template>
provide
/ inject
(跨層傳遞)用於跨多層傳遞共享狀態,而不必層層 props:
<!-- 父元件 -->
<script setup lang="ts">
import { ref, provide } from 'vue'
provide('theme', ref<'light' | 'dark'>('light'))
</script>
<!-- 子元件 -->
<script setup lang="ts">
import { inject, type Ref } from 'vue'
const theme = inject<Ref<'light' | 'dark'>>('theme')!
</script>
<script setup>
的編譯期巨集(macros)僅在 SFC 的 <script setup>
可用,由編譯器處理(不是執行期函式)。
defineProps<T>()
:宣告 props 型別defineEmits<...>()
:宣告事件defineExpose()
:控制對父層暴露的內容withDefaults(defineProps<...>(), defaults)
:給 props 預設值defineSlots<...>()
:型別標註 slotsdefineModel()
:更簡捷地定義 v-model
(Vue 3.3+)