深度解析Vue3组合式API的核心概念、使用场景和最佳实践
作者:天天进步2015
前言
Vue 3带来的最大变革之一就是组合式API(Composition API)。它不仅解决了Options API在大型项目中的局限性,更为开发者提供了一种更灵活、更优雅的代码组织方式。本文将深入探讨组合式API的核心概念、使用场景和最佳实践。
为什么需要组合式API
Options API的痛点
在Vue 2的Options API中,我们按照选项类型组织代码:
export default {
data() {
return {
count: 0,
user: null
}
},
methods: {
increment() {
this.count++
},
fetchUser() {
// 获取用户数据
}
},
mounted() {
this.fetchUser()
}
}
这种方式在小型组件中运作良好,但随着组件复杂度增加,会遇到以下问题:
- 逻辑分散:同一功能的代码被拆分到data、methods、computed等不同选项中
- 代码复用困难:mixins容易产生命名冲突,来源不清晰
- 类型推导不友好:TypeScript支持受限
组合式API的优势
组合式API通过将相关逻辑组织在一起,完美解决了上述问题:
- 逻辑聚合:相关的响应式状态和函数可以放在一起
- 更好的复用:通过组合函数(Composables)实现逻辑复用
- 完美的TypeScript支持:天然的类型推导
- 更灵活的代码组织:不受选项结构限制
核心API详解
setup函数
setup是组合式API的入口点,在组件创建之前执行:
import { ref, onMounted } from 'vue'
export default {
setup() {
const count = ref(0)
function increment() {
count.value++
}
onMounted(() => {
console.log('组件已挂载')
})
// 返回的内容会暴露给模板
return {
count,
increment
}
}
}
响应式基础
ref:基本类型的响应式
ref为基本类型创建响应式引用:
import { ref } from 'vue'
const count = ref(0)
const message = ref('Hello')
// 在JS中需要通过.value访问
console.log(count.value) // 0
count.value++
// 在模板中自动解包,不需要.value
// <div>{{ count }}</div>
reactive:对象的响应式
reactive为对象创建响应式代理:
import { reactive } from 'vue'
const state = reactive({
count: 0,
user: {
name: 'Vue',
age: 3
}
})
// 直接访问属性,不需要.value
state.count++
state.user.name = 'Vue 3'
ref vs reactive的选择
// 推荐:使用ref作为主要方式
const count = ref(0)
const user = ref({ name: 'Vue' })
// reactive适合:整体替换较少的对象
const form = reactive({
username: '',
password: ''
})
计算属性与侦听 器
computed:计算属性
import { ref, computed } from 'vue'
const count = ref(1)
const double = computed(() => count.value * 2)
// 可写的计算属性
const firstName = ref('张')
const lastName = ref('三')
const fullName = computed({
get() {
return firstName.value + lastName.value
},
set(newValue) {
[firstName.value, lastName.value] = newValue.split(' ')
}
})
watch:侦 听器
import { ref, watch } from 'vue'
const count = ref(0)
// 侦听单个ref
watch(count, (newValue, oldValue) => {
console.log(`count变化:${oldValue} -> ${newValue}`)
})
// 侦听多个源
const firstName = ref('张')
const lastName = ref('三')
watch([firstName, lastName], ([newFirst, newLast]) => {
console.log(`姓名:${newFirst}${newLast}`)
})
// 深度侦听
const state = ref({ count: 0 })
watch(state, (newValue) => {
console.log('状态改变')
}, { deep: true })
watchEffect:自动追踪依赖
import { ref, watchEffect } from 'vue'
const count = ref(0)
const double = ref(0)
// 自动追踪响应式依赖
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('组件已卸载')
})
}
}
组合函数(Composables)
组合函数是组合式API最强大的特性之一,它允许我们提取和复用有状态逻辑。
创建自定义组合函数
示例:鼠标位置追踪
// 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 }
}
使用组合函数:
import { useMouse } from './composables/useMouse'
export default {
setup() {
const { x, y } = useMouse()
return { x, y }
}
}
示例:网络请求
// 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 }
}
}
组合函数的命名约定
- 以
use开头,如useUser、useFetch - 采用驼峰命名法
- 返回对象包含响应式状态和方法
Script Setup语法糖
<script setup>是组合式API的编译时语法糖,让代码更简洁:
基础用法
<script setup>
import { ref, computed } from 'vue'
// 自动暴露给模板
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' // 组件自动注册,无需手动声明 </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>
最佳实践
1. 逻辑组织原则
按功能组织,而非按选项类型:
// ✅ 好的实践
export default {
setup() {
// 用户相关逻辑
const user = ref(null)
const fetchUser = async () => { /* ... */ }
// 计数器相关逻辑
const count = ref(0)
const increment = () => count.value++
// 搜索相关逻辑
const searchQuery = ref('')
const searchResults = computed(() => { /* ... */ })
return { user, fetchUser, count, increment, searchQuery, searchResults }
}
}
2. 提取可复用逻辑
当同一逻辑在多个组件中使用时,提取为组合函数:
// ✅ 好的实践
// 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' })
// ✅ 适用场景:表单对象
const form = reactive({
username: '',
email: '',
password: ''
})
// ❌ 避免:reactive包裹基本类型
const count = reactive(0) // 错误!
// ❌ 避免:解构reactive对象(会丢失响应性)
const { username } = reactive({ username: '' })
4. 使用toRefs保持响应性
import { reactive, toRefs } from 'vue'
function useUser() {
const state = reactive({
name: 'Vue',
age: 3
})
// 保持响应性
return toRefs(state)
}
// 使用时
const { name, age } = useUser()
5. 避免过度抽象
// ❌ 过度抽象
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 }
}
}
实战案例:待办事项应用
让我们用组合式API构建一个完整的待办事项应用:
<script setup>
import { ref, computed } from 'vue'
// 待办事项的类型定义
interface Todo {
id: number
text: string
completed: boolean
}
// 状态管理
const todos = ref<Todo[]>([])
const newTodoText = ref('')
const filter = ref<'all' | 'active' | 'completed'>('all')
// 计算属性
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>待办事项</h1>
<div class="input-section">
<input
v-model="newTodoText"
@keyup.enter="addTodo"
placeholder="输入待办事项..."
/>
<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 }} 项</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>
性能优化技巧
1. 使用shallowRef和shallowReactive
对于大型对象,使用浅层响应式可以提升性能:
import { shallowRef, shallowReactive } from 'vue'
// 只有根级属性是响应式的
const state = shallowReactive({
nested: {
count: 0 // 这个不是响应式的
}
})
// 整体替换才会触发更新
const bigData = shallowRef({
items: [...] // 大型数组
})
// 触发更新
bigData.value = { items: newItems }
2. 使用computed缓存计算结果
import { ref, computed } from 'vue'
const items = ref([...])
// ✅ 使用computed缓存
const expensiveComputed = computed(() => {
return items.value.filter(/* 复杂计算 */)
})
// ❌ 避免在方法中重复计算
function expensiveMethod() {
return items.value.filter(/* 复杂计算 */)
}
3. 合理使用watchEffect
import { ref, watchEffect } from 'vue'
const count = ref(0)
// ✅ watchEffect会自动追踪依赖
watchEffect(() => {
console.log(count.value)
})
// ❌ 不需要手动声明依赖
watch(() => count.value, (val) => {
console.log(val)
})
总结
Vue 3的组合式API带来了革命性的变化,它让我们能够:
- 更好地组织代码:相关逻辑可以放在一起,提高可读性和可维护性
- 更灵活地复用逻辑:通过组合函数实现优雅的代码复用
- 更好的类型支持:与TypeScript完美集成
- 更小的打包体积:Tree-shaking友好
组合式API并非要取代Options API,而是提供了一种更强大的选择。对于简单组件,Options API依然简洁直观;对于复杂的业务逻辑,组合式API能让代码更加优雅和可维护。
掌握组合式API,就是掌握了Vue 3的精髓。从现在开始,让我们用更优雅的方式编写Vue应用吧!
以上就是深度解析Vue3组合式API的核心概念、使用场景和最佳实践的详细内容,更多关于Vue3组合式API的资料请关注脚本之家其它相关文章!
