深度解析Vue3組合式API的核心概念、使用場(chǎng)景和最佳實(shí)踐
前言
Vue 3帶來(lái)的最大變革之一就是組合式API(Composition API)。它不僅解決了Options API在大型項(xiàng)目中的局限性,更為開(kāi)發(fā)者提供了一種更靈活、更優(yōu)雅的代碼組織方式。本文將深入探討組合式API的核心概念、使用場(chǎng)景和最佳實(shí)踐。
為什么需要組合式API
Options API的痛點(diǎn)
在Vue 2的Options API中,我們按照選項(xiàng)類(lèi)型組織代碼:
export default {
data() {
return {
count: 0,
user: null
}
},
methods: {
increment() {
this.count++
},
fetchUser() {
// 獲取用戶數(shù)據(jù)
}
},
mounted() {
this.fetchUser()
}
}
這種方式在小型組件中運(yùn)作良好,但隨著組件復(fù)雜度增加,會(huì)遇到以下問(wèn)題:
- 邏輯分散:同一功能的代碼被拆分到data、methods、computed等不同選項(xiàng)中
- 代碼復(fù)用困難:mixins容易產(chǎn)生命名沖突,來(lái)源不清晰
- 類(lèi)型推導(dǎo)不友好:TypeScript支持受限
組合式API的優(yōu)勢(shì)
組合式API通過(guò)將相關(guān)邏輯組織在一起,完美解決了上述問(wèn)題:
- 邏輯聚合:相關(guān)的響應(yīng)式狀態(tài)和函數(shù)可以放在一起
- 更好的復(fù)用:通過(guò)組合函數(shù)(Composables)實(shí)現(xiàn)邏輯復(fù)用
- 完美的TypeScript支持:天然的類(lèi)型推導(dǎo)
- 更靈活的代碼組織:不受選項(xiàng)結(jié)構(gòu)限制
核心API詳解
setup函數(shù)
setup是組合式API的入口點(diǎn),在組件創(chuàng)建之前執(zhí)行:
import { ref, onMounted } from 'vue'
export default {
setup() {
const count = ref(0)
function increment() {
count.value++
}
onMounted(() => {
console.log('組件已掛載')
})
// 返回的內(nèi)容會(huì)暴露給模板
return {
count,
increment
}
}
}
響應(yīng)式基礎(chǔ)
ref:基本類(lèi)型的響應(yīng)式
ref為基本類(lèi)型創(chuàng)建響應(yīng)式引用:
import { ref } from 'vue'
const count = ref(0)
const message = ref('Hello')
// 在JS中需要通過(guò).value訪問(wèn)
console.log(count.value) // 0
count.value++
// 在模板中自動(dòng)解包,不需要.value
// <div>{{ count }}</div>
reactive:對(duì)象的響應(yīng)式
reactive為對(duì)象創(chuàng)建響應(yīng)式代理:
import { reactive } from 'vue'
const state = reactive({
count: 0,
user: {
name: 'Vue',
age: 3
}
})
// 直接訪問(wèn)屬性,不需要.value
state.count++
state.user.name = 'Vue 3'
ref vs reactive的選擇
// 推薦:使用ref作為主要方式
const count = ref(0)
const user = ref({ name: 'Vue' })
// reactive適合:整體替換較少的對(duì)象
const form = reactive({
username: '',
password: ''
})
計(jì)算屬性與偵聽(tīng) 器
computed:計(jì)算屬性
import { ref, computed } from 'vue'
const count = ref(1)
const double = computed(() => count.value * 2)
// 可寫(xiě)的計(jì)算屬性
const firstName = ref('張')
const lastName = ref('三')
const fullName = computed({
get() {
return firstName.value + lastName.value
},
set(newValue) {
[firstName.value, lastName.value] = newValue.split(' ')
}
})
watch:偵 聽(tīng)器
import { ref, watch } from 'vue'
const count = ref(0)
// 偵聽(tīng)單個(gè)ref
watch(count, (newValue, oldValue) => {
console.log(`count變化:${oldValue} -> ${newValue}`)
})
// 偵聽(tīng)多個(gè)源
const firstName = ref('張')
const lastName = ref('三')
watch([firstName, lastName], ([newFirst, newLast]) => {
console.log(`姓名:${newFirst}${newLast}`)
})
// 深度偵聽(tīng)
const state = ref({ count: 0 })
watch(state, (newValue) => {
console.log('狀態(tài)改變')
}, { deep: true })
watchEffect:自動(dòng)追蹤依賴
import { ref, watchEffect } from 'vue'
const count = ref(0)
const double = ref(0)
// 自動(dòng)追蹤響應(yīng)式依賴
watchEffect(() => {
double.value = count.value * 2
console.log(`count: ${count.value}, double: ${double.value}`)
})
生命周期鉤子
組合式API中的生命周期鉤子:
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
} from 'vue'
export default {
setup() {
onBeforeMount(() => {
console.log('組件掛載前')
})
onMounted(() => {
console.log('組件已掛載')
})
onBeforeUpdate(() => {
console.log('組件更新前')
})
onUpdated(() => {
console.log('組件已更新')
})
onBeforeUnmount(() => {
console.log('組件卸載前')
})
onUnmounted(() => {
console.log('組件已卸載')
})
}
}
組合函數(shù)(Composables)
組合函數(shù)是組合式API最強(qiáng)大的特性之一,它允許我們提取和復(fù)用有狀態(tài)邏輯。
創(chuàng)建自定義組合函數(shù)
示例:鼠標(biāo)位置追蹤
// composables/useMouse.js
import { ref, onMounted, onUnmounted } from 'vue'
export function useMouse() {
const x = ref(0)
const y = ref(0)
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
onMounted(() => {
window.addEventListener('mousemove', update)
})
onUnmounted(() => {
window.removeEventListener('mousemove', update)
})
return { x, y }
}
使用組合函數(shù):
import { useMouse } from './composables/useMouse'
export default {
setup() {
const { x, y } = useMouse()
return { x, y }
}
}
示例:網(wǎng)絡(luò)請(qǐng)求
// composables/useFetch.js
import { ref, watchEffect, toValue } from 'vue'
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
const loading = ref(false)
watchEffect(async () => {
loading.value = true
data.value = null
error.value = null
try {
const urlValue = toValue(url)
const res = await fetch(urlValue)
data.value = await res.json()
} catch (e) {
error.value = e
} finally {
loading.value = false
}
})
return { data, error, loading }
}
使用示例:
import { ref } from 'vue'
import { useFetch } from './composables/useFetch'
export default {
setup() {
const userId = ref(1)
const url = computed(() => `/api/users/${userId.value}`)
const { data, error, loading } = useFetch(url)
return { data, error, loading, userId }
}
}
組合函數(shù)的命名約定
- 以
use開(kāi)頭,如useUser、useFetch - 采用駝峰命名法
- 返回對(duì)象包含響應(yīng)式狀態(tài)和方法
Script Setup語(yǔ)法糖
<script setup>是組合式API的編譯時(shí)語(yǔ)法糖,讓代碼更簡(jiǎn)潔:
基礎(chǔ)用法
<script setup>
import { ref, computed } from 'vue'
// 自動(dòng)暴露給模板
const count = ref(0)
const double = computed(() => count.value * 2)
function increment() {
count.value++
}
</script>
<template>
<div>
<p>Count: {{ count }}</p>
<p>Double: {{ double }}</p>
<button @click="increment">增加</button>
</div>
</template>
使用組件
<script setup> import MyComponent from './MyComponent.vue' // 組件自動(dòng)注冊(cè),無(wú)需手動(dòng)聲明 </script> <template> <MyComponent /> </template>
defineProps和defineEmits
<script setup>
// 定義props
const props = defineProps({
title: String,
count: {
type: Number,
default: 0
}
})
// 定義emits
const emit = defineEmits(['update', 'delete'])
function handleUpdate() {
emit('update', props.count + 1)
}
</script>
<template>
<div>
<h3>{{ title }}</h3>
<p>{{ count }}</p>
<button @click="handleUpdate">更新</button>
</div>
</template>
defineExpose
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
// 顯式暴露給父組件
defineExpose({
count,
increment
})
</script>
最佳實(shí)踐
1. 邏輯組織原則
按功能組織,而非按選項(xiàng)類(lèi)型:
// ? 好的實(shí)踐
export default {
setup() {
// 用戶相關(guān)邏輯
const user = ref(null)
const fetchUser = async () => { /* ... */ }
// 計(jì)數(shù)器相關(guān)邏輯
const count = ref(0)
const increment = () => count.value++
// 搜索相關(guān)邏輯
const searchQuery = ref('')
const searchResults = computed(() => { /* ... */ })
return { user, fetchUser, count, increment, searchQuery, searchResults }
}
}
2. 提取可復(fù)用邏輯
當(dāng)同一邏輯在多個(gè)組件中使用時(shí),提取為組合函數(shù):
// ? 好的實(shí)踐
// composables/useCounter.js
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const increment = () => count.value++
const decrement = () => count.value--
const reset = () => count.value = initialValue
return { count, increment, decrement, reset }
}
3. 合理使用ref和reactive
// ? 推薦:使用ref
const count = ref(0)
const user = ref({ name: 'Vue' })
// ? 適用場(chǎng)景:表單對(duì)象
const form = reactive({
username: '',
email: '',
password: ''
})
// ? 避免:reactive包裹基本類(lèi)型
const count = reactive(0) // 錯(cuò)誤!
// ? 避免:解構(gòu)reactive對(duì)象(會(huì)丟失響應(yīng)性)
const { username } = reactive({ username: '' })
4. 使用toRefs保持響應(yīng)性
import { reactive, toRefs } from 'vue'
function useUser() {
const state = reactive({
name: 'Vue',
age: 3
})
// 保持響應(yīng)性
return toRefs(state)
}
// 使用時(shí)
const { name, age } = useUser()
5. 避免過(guò)度抽象
// ? 過(guò)度抽象
function useCount() {
return ref(0)
}
// ? 合理抽象:包含有意義的邏輯
function useCounter(initialValue = 0) {
const count = ref(initialValue)
const increment = () => count.value++
const decrement = () => count.value--
return { count, increment, decrement }
}
6. TypeScript集成
import { ref, computed } from 'vue'
interface User {
id: number
name: string
email: string
}
export default {
setup() {
const user = ref<User | null>(null)
const userName = computed(() => user.value?.name ?? '未登錄')
async function fetchUser(id: number): Promise<void> {
const response = await fetch(`/api/users/${id}`)
user.value = await response.json()
}
return { user, userName, fetchUser }
}
}
實(shí)戰(zhàn)案例:待辦事項(xiàng)應(yīng)用
讓我們用組合式API構(gòu)建一個(gè)完整的待辦事項(xiàng)應(yīng)用:
<script setup>
import { ref, computed } from 'vue'
// 待辦事項(xiàng)的類(lèi)型定義
interface Todo {
id: number
text: string
completed: boolean
}
// 狀態(tài)管理
const todos = ref<Todo[]>([])
const newTodoText = ref('')
const filter = ref<'all' | 'active' | 'completed'>('all')
// 計(jì)算屬性
const filteredTodos = computed(() => {
switch (filter.value) {
case 'active':
return todos.value.filter(t => !t.completed)
case 'completed':
return todos.value.filter(t => t.completed)
default:
return todos.value
}
})
const activeCount = computed(() =>
todos.value.filter(t => !t.completed).length
)
// 方法
function addTodo() {
if (newTodoText.value.trim()) {
todos.value.push({
id: Date.now(),
text: newTodoText.value,
completed: false
})
newTodoText.value = ''
}
}
function removeTodo(id: number) {
todos.value = todos.value.filter(t => t.id !== id)
}
function toggleTodo(id: number) {
const todo = todos.value.find(t => t.id === id)
if (todo) {
todo.completed = !todo.completed
}
}
function clearCompleted() {
todos.value = todos.value.filter(t => !t.completed)
}
</script>
<template>
<div class="todo-app">
<h1>待辦事項(xiàng)</h1>
<div class="input-section">
<input
v-model="newTodoText"
@keyup.enter="addTodo"
placeholder="輸入待辦事項(xiàng)..."
/>
<button @click="addTodo">添加</button>
</div>
<div class="filters">
<button
:class="{ active: filter === 'all' }"
@click="filter = 'all'"
>
全部
</button>
<button
:class="{ active: filter === 'active' }"
@click="filter = 'active'"
>
未完成
</button>
<button
:class="{ active: filter === 'completed' }"
@click="filter = 'completed'"
>
已完成
</button>
</div>
<ul class="todo-list">
<li
v-for="todo in filteredTodos"
:key="todo.id"
:class="{ completed: todo.completed }"
>
<input
type="checkbox"
:checked="todo.completed"
@change="toggleTodo(todo.id)"
/>
<span>{{ todo.text }}</span>
<button @click="removeTodo(todo.id)">刪除</button>
</li>
</ul>
<div class="footer">
<span>剩余 {{ activeCount }} 項(xiàng)</span>
<button
v-if="activeCount < todos.length"
@click="clearCompleted"
>
清除已完成
</button>
</div>
</div>
</template>
<style scoped>
.todo-app {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.input-section {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.input-section input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
.filters {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.filters button {
padding: 8px 16px;
border: 1px solid #ddd;
background: white;
border-radius: 4px;
cursor: pointer;
}
.filters button.active {
background: #42b983;
color: white;
border-color: #42b983;
}
.todo-list {
list-style: none;
padding: 0;
}
.todo-list li {
display: flex;
align-items: center;
gap: 10px;
padding: 10px;
border-bottom: 1px solid #eee;
}
.todo-list li.completed span {
text-decoration: line-through;
color: #999;
}
.footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #eee;
}
</style>
性能優(yōu)化技巧
1. 使用shallowRef和shallowReactive
對(duì)于大型對(duì)象,使用淺層響應(yīng)式可以提升性能:
import { shallowRef, shallowReactive } from 'vue'
// 只有根級(jí)屬性是響應(yīng)式的
const state = shallowReactive({
nested: {
count: 0 // 這個(gè)不是響應(yīng)式的
}
})
// 整體替換才會(huì)觸發(fā)更新
const bigData = shallowRef({
items: [...] // 大型數(shù)組
})
// 觸發(fā)更新
bigData.value = { items: newItems }
2. 使用computed緩存計(jì)算結(jié)果
import { ref, computed } from 'vue'
const items = ref([...])
// ? 使用computed緩存
const expensiveComputed = computed(() => {
return items.value.filter(/* 復(fù)雜計(jì)算 */)
})
// ? 避免在方法中重復(fù)計(jì)算
function expensiveMethod() {
return items.value.filter(/* 復(fù)雜計(jì)算 */)
}
3. 合理使用watchEffect
import { ref, watchEffect } from 'vue'
const count = ref(0)
// ? watchEffect會(huì)自動(dòng)追蹤依賴
watchEffect(() => {
console.log(count.value)
})
// ? 不需要手動(dòng)聲明依賴
watch(() => count.value, (val) => {
console.log(val)
})
總結(jié)
Vue 3的組合式API帶來(lái)了革命性的變化,它讓我們能夠:
- 更好地組織代碼:相關(guān)邏輯可以放在一起,提高可讀性和可維護(hù)性
- 更靈活地復(fù)用邏輯:通過(guò)組合函數(shù)實(shí)現(xiàn)優(yōu)雅的代碼復(fù)用
- 更好的類(lèi)型支持:與TypeScript完美集成
- 更小的打包體積:Tree-shaking友好
組合式API并非要取代Options API,而是提供了一種更強(qiáng)大的選擇。對(duì)于簡(jiǎn)單組件,Options API依然簡(jiǎn)潔直觀;對(duì)于復(fù)雜的業(yè)務(wù)邏輯,組合式API能讓代碼更加優(yōu)雅和可維護(hù)。
掌握組合式API,就是掌握了Vue 3的精髓。從現(xiàn)在開(kāi)始,讓我們用更優(yōu)雅的方式編寫(xiě)Vue應(yīng)用吧!
以上就是深度解析Vue3組合式API的核心概念、使用場(chǎng)景和最佳實(shí)踐的詳細(xì)內(nèi)容,更多關(guān)于Vue3組合式API的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue用vis插件如何實(shí)現(xiàn)網(wǎng)絡(luò)拓?fù)鋱D
這篇文章主要介紹了vue用vis插件如何實(shí)現(xiàn)網(wǎng)絡(luò)拓?fù)鋱D,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10
vue實(shí)現(xiàn)書(shū)籍購(gòu)物車(chē)功能
這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)書(shū)籍購(gòu)物車(chē)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10
vue封裝自定義指令之動(dòng)態(tài)顯示title操作(溢出顯示,不溢出不顯示)
這篇文章主要介紹了vue封裝自定義指令之動(dòng)態(tài)顯示title操作(溢出顯示,不溢出不顯示),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11
Vue實(shí)現(xiàn)讓頁(yè)面加載時(shí)請(qǐng)求后臺(tái)接口數(shù)據(jù)
這篇文章主要介紹了Vue實(shí)現(xiàn)讓頁(yè)面加載時(shí)請(qǐng)求后臺(tái)接口數(shù)據(jù)2022-08-08
vue單文件組件lint error自動(dòng)fix與styleLint報(bào)錯(cuò)自動(dòng)fix詳解
這篇文章主要給大家介紹了關(guān)于vue單文件組件lint error自動(dòng)fix與styleLint報(bào)錯(cuò)自動(dòng)fix的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-01-01
vue 數(shù)據(jù)遍歷篩選 過(guò)濾 排序的應(yīng)用操作
這篇文章主要介紹了vue 數(shù)據(jù)遍歷篩選 過(guò)濾 排序的應(yīng)用操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11
Vue使用Cropper實(shí)現(xiàn)圖片裁剪功能
圖片裁剪功能無(wú)論是用戶頭像的裁剪,還是圖片內(nèi)容的精確調(diào)整,都成為了提升用戶體驗(yàn)的關(guān)鍵一環(huán),本文將詳細(xì)介紹如何在Vue.js項(xiàng)目中集成并使用Cropper.js實(shí)現(xiàn)一個(gè)強(qiáng)大的圖片裁剪組件,需要的可以參考下2024-11-11
vue-cli的build的文件夾下沒(méi)有dev-server.js文件配置mock數(shù)據(jù)的方法
這篇文章主要介紹了vue-cli的build的文件夾下沒(méi)有dev-server.js文件配置mock數(shù)據(jù)的方法,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-04-04
vue項(xiàng)目使用高德地圖時(shí)報(bào)錯(cuò):AMap?is?not?defined解決辦法
這篇文章主要給大家介紹了關(guān)于vue項(xiàng)目使用高德地圖時(shí)報(bào)錯(cuò):AMap?is?not?defined的解決辦法,"AMap is not defined"是一個(gè)錯(cuò)誤提示,意思是在代碼中沒(méi)有找到定義的AMap,需要的朋友可以參考下2023-12-12
Monaco-editor 的 JSON Schema 配置及使用介紹
這篇文章主要為大家介紹了Monaco-editor 的 JSON Schema 配置及使用介紹,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10

