
前言Vue 3 引入了全新的組合式 APIComposition API它解決了大型組件中邏輯分散的問題讓代碼更易組織和復(fù)用。本教程將帶你從零開始掌握組合式 API 的核心用法并通過實(shí)戰(zhàn)項(xiàng)目鞏固所學(xué)知識。一、為什么需要組合式 API1.1 Options API 的痛點(diǎn)在 Vue 2 的 Options API 中組件代碼按選項(xiàng)類型組織script export default { data() { return { searchKeyword: , userList: [], cartItems: [] } }, computed: { filteredUsers() { /* 搜索相關(guān)邏輯 */ }, cartTotal() { /* 購物車相關(guān)邏輯 */ } }, methods: { searchUsers() { /* 搜索相關(guān)邏輯 */ }, addToCart() { /* 購物車相關(guān)邏輯 */ }, removeFromCart() { /* 購物車相關(guān)邏輯 */ } }, mounted() { this.searchUsers() this.loadCart() } } /script問題搜索功能的邏輯分散在 data、computed、methods、mounted 中當(dāng)組件變大后很難追蹤一個完整的功能邏輯。1.2 Composition API 的優(yōu)勢組合式 API 按功能組織代碼script setup // 搜索功能 —— 所有相關(guān)代碼集中在一起 const { searchKeyword, filteredUsers, searchUsers } useSearch() // 購物車功能 —— 所有相關(guān)代碼集中在一起 const { cartItems, cartTotal, addToCart, removeFromCart } useCart() /script對比項(xiàng)Options APIComposition API代碼組織按選項(xiàng)類型按功能邏輯邏輯復(fù)用mixins易沖突自定義組合式函數(shù)TypeScript支持較弱原生支持代碼量較少樣板稍多但更清晰適用場景小型組件中大型組件二、環(huán)境搭建2.1 創(chuàng)建 Vue 3 項(xiàng)目# 使用 Vite 創(chuàng)建推薦npmcreate vitelatest my-vue-app ----templatevuecdmy-vue-appnpminstallnpmrun dev# 使用 Vue CLInpminstall-gvue/cli vue create my-vue-app2.2 項(xiàng)目結(jié)構(gòu)my-vue-app/ ├── src/ │ ├── components/ │ ├── composables/ # 組合式函數(shù)目錄 │ ├── App.vue │ └── main.js ├── index.html ├── package.json └── vite.config.js三、script setup語法script setup是組合式 API 的編譯時語法糖讓代碼更簡潔。3.1 基本用法script setup import { ref, computed } from vue // 響應(yīng)式數(shù)據(jù) const count ref(0) // 計算屬性 const doubleCount computed(() count.value * 2) // 方法 function increment() { count.value } /script template div p計數(shù): {{ count }}/p p雙倍: {{ doubleCount }}/p button clickincrement1/button /div /template3.2 與普通script的區(qū)別!-- 普通 script需要 return -- script import { ref } from vue export default { setup() { const count ref(0) return { count } // 必須返回才能在模板中使用 } } /script !-- script setup自動暴露 -- script setup import { ref } from vue const count ref(0) // 自動暴露給模板 /script四、核心 API 詳解4.1 ref —— 基本類型響應(yīng)式script setup import { ref } from vue // 字符串 const message ref(Hello Vue 3) // 數(shù)字 const count ref(0) // 布爾值 const isVisible ref(false) // 對象ref 也可以包裝對象 const user ref({ name: 張三, age: 25 }) // 修改值注意 .value function updateMessage() { message.value Updated! } function updateUser() { // 直接修改 ref 包裝的對象屬性 user.value.name 李四 // 或整體替換 user.value { name: 王五, age: 30 } } /script template !-- 模板中不需要 .value -- p{{ message }}/p p{{ count }}/p p{{ user.name }} - {{ user.age }}/p /template注意在script中訪問 ref 需要.value在template中不需要。4.2 reactive —— 對象響應(yīng)式script setup import { reactive } from vue // reactive 用于對象/數(shù)組不需要 .value const state reactive({ user: { name: 張三, age: 25 }, settings: { theme: light, language: zh-CN } }) // 直接修改 function updateName() { state.user.name 李四 } // 注意reactive 不能替換整個對象 function wrongReplace() { // state reactive({...}) // ? 這樣會失去響應(yīng)性 } // 正確做法逐個屬性修改或用 Object.assign function correctReplace() { Object.assign(state.user, { name: 王五, age: 30 }) } /script4.3 ref vs reactive 選擇// 推薦使用 ref因?yàn)樗恢耤onstnameref(張三)// 基本類型constuserref({age:25})// 對象constlistref([])// 數(shù)組// ref 可以整體替換reactive 不行user.value{name:李四}// ? ref 可以// state { name: 李四 } // ? reactive 不行// 解構(gòu)ref 解構(gòu)不丟失響應(yīng)性template 中自動 unwrap// reactive 解構(gòu)會丟失響應(yīng)性conststatereactive({a:1,b:2})let{a,b}state// ? a, b 不再是響應(yīng)式// 正確做法使用 toRefsimport{toRefs}fromvueconst{a,b}toRefs(state)// ? 保持響應(yīng)性4.4 computed —— 計算屬性script setup import { ref, computed } from vue const firstName ref(張) const lastName ref(三) // 只讀計算屬性 const fullName computed(() ${firstName.value}${lastName.value}) // 可寫計算屬性 const fullNameWritable computed({ get() { return ${firstName.value}${lastName.value} }, set(newValue) { const names newValue.split( ) firstName.value names[0] || lastName.value names[1] || } }) // 計算屬性依賴其他計算屬性 const greeting computed(() Hello, ${fullName.value}!) // 列表過濾 const items ref([ { id: 1, name: 蘋果, price: 5 }, { id: 2, name: 香蕉, price: 3 }, { id: 3, name: 橙子, price: 8 } ]) const minPrice ref(0) const filteredItems computed(() items.value.filter(item item.price minPrice.value) ) /script4.5 watch —— 偵聽器script setup import { ref, watch, watchEffect } from vue const keyword ref() const page ref(1) // 1. 偵聽單個 ref watch(keyword, (newValue, oldValue) { console.log(關(guān)鍵詞從 ${oldValue} 變?yōu)?${newValue}) }) // 2. 偵聽多個數(shù)據(jù)源 watch([keyword, page], ([newKeyword, newPage], [oldKeyword, oldPage]) { console.log(關(guān)鍵詞: ${newKeyword}, 頁碼: ${newPage}) }) // 3. 偵聽對象屬性需要 getter 函數(shù) const user ref({ name: 張三, age: 25 }) watch( () user.value.age, (newAge, oldAge) { console.log(年齡從 ${oldAge} 變?yōu)?${newAge}) } ) // 4. 深度偵聽偵聽對象內(nèi)部變化 watch( user, (newUser) { console.log(用戶信息變化:, newUser) }, { deep: true } ) // 5. 立即執(zhí)行 watch( keyword, (newVal) { console.log(立即執(zhí)行一次:, newVal) }, { immediate: true } ) // 6. watchEffect —— 自動追蹤依賴立即執(zhí)行 watchEffect(() { console.log(當(dāng)前關(guān)鍵詞: ${keyword.value}, 頁碼: ${page.value}) // 自動追蹤 keyword 和 page任一變化都會重新執(zhí)行 }) /script4.6 生命周期鉤子script setup import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from vue // 對應(yīng) Vue 2 的 beforeCreate / created直接在 setup 中寫 console.log(組件初始化) onBeforeMount(() { console.log(掛載前) }) onMounted(() { console.log(已掛載可以訪問 DOM) // 適合初始化數(shù)據(jù)請求、添加事件監(jiān)聽、初始化第三方庫 }) onBeforeUpdate(() { console.log(更新前) }) onUpdated(() { console.log(已更新) }) onBeforeUnmount(() { console.log(卸載前) // 適合清除定時器、移除事件監(jiān)聽 }) onUnmounted(() { console.log(已卸載) }) /script五、組件通信5.1 父傳子 —— defineProps!-- 子組件 Child.vue -- script setup // 方式一運(yùn)行時聲明 const props defineProps({ title: String, count: { type: Number, default: 0 }, items: { type: Array, default: () [] } }) // 方式二TypeScript 類型聲明推薦 // const props defineProps{ // title: string // count?: number // items?: string[] // }() /script template div h2{{ title }}/h2 p計數(shù): {{ count }}/p ul li v-foritem in items :keyitem{{ item }}/li /ul /div /template!-- 父組件 -- script setup import Child from ./Child.vue /script template Child title用戶列表 :count5 :items[張三, 李四, 王五] / /template5.2 子傳父 —— defineEmits!-- 子組件 -- script setup const emit defineEmits([update, delete]) function handleClick() { emit(update, { id: 1, name: 張三 }) } function handleDelete() { emit(delete, 1) } /script template button clickhandleClick更新/button button clickhandleDelete刪除/button /template!-- 父組件 -- script setup import Child from ./Child.vue function handleUpdate(data) { console.log(收到更新:, data) } function handleDelete(id) { console.log(刪除 ID:, id) } /script template Child updatehandleUpdate deletehandleDelete / /template5.3 雙向綁定 —— defineModel!-- 子組件 -- script setup // Vue 3.4 推薦使用 defineModel const modelValue defineModel() function updateValue() { modelValue.value 新值 } /script template input v-modelmodelValue / button clickupdateValue設(shè)為新值/button /template!-- 父組件 -- script setup import { ref } from vue import Child from ./Child.vue const text ref(初始值) /script template Child v-modeltext / p父組件值: {{ text }}/p /template5.4 暴露方法 —— defineExpose!-- 子組件 -- script setup import { ref } from vue const count ref(0) function reset() { count.value 0 } function increment() { count.value } // 暴露給父組件 defineExpose({ reset, increment, count }) /script!-- 父組件 -- script setup import { ref, onMounted } from vue import Child from ./Child.vue const childRef ref(null) onMounted(() { // 通過 ref 調(diào)用子組件方法 childRef.value.increment() }) function handleReset() { childRef.value.reset() } /script template Child refchildRef / button clickhandleReset重置子組件/button /template六、組合式函數(shù) —— 邏輯復(fù)用6.1 創(chuàng)建組合式函數(shù)組合式函數(shù)是 Vue 3 中復(fù)用邏輯的推薦方式命名約定以use開頭// composables/useCounter.jsimport{ref,computed}fromvueexportfunctionuseCounter(initialValue0,step1){constcountref(initialValue)constdoublecomputed(()count.value*2)functionincrement(){count.valuestep}functiondecrement(){count.value-step}functionreset(){count.valueinitialValue}return{count,double,increment,decrement,reset}}6.2 使用組合式函數(shù)script setup import { useCounter } from /composables/useCounter const { count, double, increment, decrement, reset } useCounter(10, 2) /script template div p計數(shù): {{ count }}/p p雙倍: {{ double }}/p button clickincrement{{ 2 }}/button button clickdecrement-{{ 2 }}/button button clickreset重置/button /div /template6.3 實(shí)用組合式函數(shù)示例useFetch —— 數(shù)據(jù)請求// composables/useFetch.jsimport{ref,watchEffect}fromvueexportfunctionuseFetch(url){constdataref(null)consterrorref(null)constloadingref(false)asyncfunctionfetchData(){loading.valuetrueerror.valuenulltry{constresponseawaitfetch(url.value||url)if(!response.ok)thrownewError(HTTP${response.status})data.valueawaitresponse.json()}catch(err){error.valueerr.message}finally{loading.valuefalse}}// 如果 url 是 ref自動重新請求if(typeofurlobjecturl.value!undefined){watchEffect(fetchData)}else{fetchData()}return{data,error,loading,refresh:fetchData}}useLocalStorage —— 本地存儲// composables/useLocalStorage.jsimport{ref,watch}fromvueexportfunctionuseLocalStorage(key,defaultValue){conststoredlocalStorage.getItem(key)constdataref(stored?JSON.parse(stored):defaultValue)watch(data,(newVal){localStorage.setItem(key,JSON.stringify(newVal))},{deep:true})returndata}useMouse —— 鼠標(biāo)位置追蹤// composables/useMouse.jsimport{ref,onMounted,onUnmounted}fromvueexportfunctionuseMouse(){constxref(0)constyref(0)functionupdate(event){x.valueevent.clientX y.valueevent.clientY}onMounted((){window.addEventListener(mousemove,update)})onUnmounted((){window.removeEventListener(mousemove,update)})return{x,y}}6.4 在組件中使用script setup import { useFetch } from /composables/useFetch import { useLocalStorage } from /composables/useLocalStorage import { useMouse } from /composables/useMouse // 數(shù)據(jù)請求 const { data: users, loading, error, refresh } useFetch(/api/users) // 本地存儲 const theme useLocalStorage(theme, light) // 鼠標(biāo)位置 const { x, y } useMouse() /script template div !-- 數(shù)據(jù)請求 -- div v-ifloading加載中.../div div v-else-iferror錯誤: {{ error }}/div ul v-else li v-foruser in users :keyuser.id{{ user.name }}/li /ul button clickrefresh刷新/button !-- 主題切換 -- button clicktheme theme light ? dark : light 當(dāng)前主題: {{ theme }} /button !-- 鼠標(biāo)位置 -- p鼠標(biāo)位置: {{ x }}, {{ y }}/p /div /template七、實(shí)戰(zhàn)Todo List 應(yīng)用!-- components/TodoApp.vue -- script setup import { ref, computed } from vue import { useLocalStorage } from /composables/useLocalStorage // 從本地存儲恢復(fù) const todos useLocalStorage(todos, []) const newTodoText ref() const filter ref(all) // 添加 function addTodo() { const text newTodoText.value.trim() if (!text) return todos.value.push({ id: Date.now(), text, done: false, createdAt: new Date().toISOString() }) newTodoText.value } // 刪除 function removeTodo(id) { todos.value todos.value.filter(t t.id ! id) } // 切換完成狀態(tài) function toggleTodo(id) { const todo todos.value.find(t t.id id) if (todo) todo.done !todo.done } // 清除已完成 function clearCompleted() { todos.value todos.value.filter(t !t.done) } // 過濾后的列表 const filteredTodos computed(() { switch (filter.value) { case active: return todos.value.filter(t !t.done) case completed: return todos.value.filter(t t.done) default: return todos.value } }) // 統(tǒng)計 const remaining computed(() todos.value.filter(t !t.done).length) const total computed(() todos.value.length) /script template div classtodo-app h2Todo List/h2 !-- 輸入 -- div classinput-row input v-modelnewTodoText keyup.enteraddTodo placeholder輸入待辦事項(xiàng)... / button clickaddTodo添加/button /div !-- 過濾 -- div classfilter-row button :class{ active: filter all } clickfilter all 全部 ({{ total }}) /button button :class{ active: filter active } clickfilter active 未完成 ({{ remaining }}) /button button :class{ active: filter completed } clickfilter completed 已完成 ({{ total - remaining }}) /button /div !-- 列表 -- ul classtodo-list li v-fortodo in filteredTodos :keytodo.id input typecheckbox :checkedtodo.done changetoggleTodo(todo.id) / span :class{ done: todo.done }{{ todo.text }}/span button clickremoveTodo(todo.id)刪除/button /li /ul button v-iftotal - remaining 0 clickclearCompleted 清除已完成 /button /div /template style scoped .todo-app { max-width: 500px; margin: 0 auto; padding: 20px; } .input-row { display: flex; gap: 8px; margin-bottom: 16px; } .input-row input { flex: 1; padding: 8px; } .filter-row { display: flex; gap: 8px; margin-bottom: 16px; } .filter-row button { padding: 4px 12px; } .filter-row button.active { background: #42b883; color: white; } .todo-list { list-style: none; padding: 0; } .todo-list li { display: flex; align-items: center; gap: 8px; padding: 8px 0; } .todo-list li .done { text-decoration: line-through; color: #999; } .todo-list li button { margin-left: auto; } /styleAPI 速查表API用途示例ref()創(chuàng)建響應(yīng)式數(shù)據(jù)const count ref(0)reactive()創(chuàng)建響應(yīng)式對象const state reactive({...})computed()計算屬性const double computed(() count.value * 2)watch()偵聽特定數(shù)據(jù)watch(count, (newVal) {...})watchEffect()自動追蹤依賴watchEffect(() { console.log(count.value) })onMounted()掛載后鉤子onMounted(() { fetchData() })onUnmounted()卸載前鉤子onUnmounted(() { clearInterval(timer) })defineProps()接收父組件數(shù)據(jù)const props defineProps({...})defineEmits()定義事件const emit defineEmits([...])defineModel()雙向綁定const value defineModel()defineExpose()暴露方法defineExpose({ method })toRefs()解構(gòu)保持響應(yīng)性const { a, b } toRefs(state)總結(jié)本教程涵蓋了 Vue 3 組合式 API 的核心內(nèi)容設(shè)計理念理解從 Options API 到 Composition API 的演進(jìn)script setup更簡潔的語法糖核心 APIref、reactive、computed、watch、生命周期組件通信props、emits、model、expose邏輯復(fù)用組合式函數(shù)useFetch、useLocalStorage、useMouse實(shí)戰(zhàn)項(xiàng)目完整的 Todo List 應(yīng)用組合式 API 是 Vue 3 最重要的特性之一它讓代碼組織更加靈活、邏輯復(fù)用更加簡單。建議在實(shí)際項(xiàng)目中多加練習(xí)逐步從 Options API 過渡到 Composition API。