前言
Vue 3 的发布带来了革命性的变化,其中最引人注目的就是 Composition API 的引入。这一新特性彻底改变了我们编写 Vue 组件的方式,提供了更加灵活和强大的代码组织能力。本文将深入探讨 Vue3 Composition API 的核心特性,从基础语法到复杂组件开发的完整实践,帮助开发者快速掌握这一现代前端开发技术。
什么是 Composition API
核心概念
Composition API 是 Vue 3 中引入的一种新的组件逻辑组织方式,它允许我们使用函数来组合和复用组件逻辑,而不是传统的选项式 API。与 Vue 2 的 Options API 相比,Composition API 提供了更灵活的代码组织方式,特别适合处理复杂的组件逻辑。
主要优势
- 更好的逻辑复用:通过函数封装,可以轻松在多个组件间共享逻辑
- 更清晰的代码结构:将相关的逻辑组织在一起,而不是分散在不同选项中
- 更强大的类型支持:与 TypeScript 集成更好,提供更好的开发体验
- 更灵活的组件开发:可以根据需要动态地添加或移除功能
基础语法详解
1. setup 函数
setup 是 Composition API 的入口函数,它在组件实例创建之前执行,接收两个参数:props 和 context。
import { ref, reactive } from 'vue'
export default {
props: {
title: String
},
setup(props, context) {
// 在这里定义响应式数据和逻辑
const count = ref(0)
const user = reactive({ name: 'John', age: 30 })
return {
count,
user
}
}
}
2. 响应式数据
Composition API 提供了两种主要的响应式数据处理方式:ref 和 reactive。
Ref 的使用
import { ref } from 'vue'
export default {
setup() {
// 创建基本类型的响应式数据
const count = ref(0)
const message = ref('Hello Vue')
// 访问和修改值
console.log(count.value) // 0
count.value = 10
return {
count,
message
}
}
}
Reactive 的使用
import { reactive } from 'vue'
export default {
setup() {
// 创建对象类型的响应式数据
const state = reactive({
count: 0,
user: {
name: 'John',
age: 30
}
})
// 直接修改属性
state.count = 10
state.user.name = 'Jane'
return {
state
}
}
}
3. 计算属性和监听器
计算属性
import { ref, computed } from 'vue'
export default {
setup() {
const firstName = ref('John')
const lastName = ref('Doe')
// 基础计算属性
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`
})
// 带 getter 和 setter 的计算属性
const reversedName = computed({
get: () => {
return firstName.value.split('').reverse().join('')
},
set: (value) => {
firstName.value = value.split('').reverse().join('')
}
})
return {
firstName,
lastName,
fullName,
reversedName
}
}
}
监听器
import { ref, watch, watchEffect } from 'vue'
export default {
setup() {
const count = ref(0)
const name = ref('John')
// 基础监听器
watch(count, (newValue, oldValue) => {
console.log(`count changed from ${oldValue} to ${newValue}`)
})
// 监听多个源
watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
console.log(`count: ${oldCount} -> ${newCount}, name: ${oldName} -> ${newName}`)
})
// 使用 watchEffect
watchEffect(() => {
console.log(`Current count is: ${count.value}`)
})
return {
count,
name
}
}
}
实际应用案例
1. 用户管理组件
让我们创建一个完整的用户管理组件来演示 Composition API 的强大功能:
<template>
<div class="user-manager">
<h2>用户管理</h2>
<!-- 搜索和过滤 -->
<div class="search-section">
<input
v-model="searchTerm"
placeholder="搜索用户..."
class="search-input"
/>
<select v-model="filterRole" class="role-filter">
<option value="">所有角色</option>
<option value="admin">管理员</option>
<option value="user">普通用户</option>
<option value="guest">访客</option>
</select>
</div>
<!-- 用户列表 -->
<div class="user-list">
<div
v-for="user in filteredUsers"
:key="user.id"
class="user-item"
>
<div class="user-info">
<h3>{{ user.name }}</h3>
<p>邮箱: {{ user.email }}</p>
<p>角色: {{ user.role }}</p>
</div>
<div class="user-actions">
<button @click="editUser(user)" class="btn-edit">编辑</button>
<button @click="deleteUser(user.id)" class="btn-delete">删除</button>
</div>
</div>
</div>
<!-- 分页 -->
<div class="pagination">
<button
@click="currentPage--"
:disabled="currentPage === 1"
class="page-btn"
>
上一页
</button>
<span>第 {{ currentPage }} 页,共 {{ totalPages }} 页</span>
<button
@click="currentPage++"
:disabled="currentPage === totalPages"
class="page-btn"
>
下一页
</button>
</div>
<!-- 添加用户表单 -->
<div class="add-user-form">
<h3>{{ isEditing ? '编辑用户' : '添加新用户' }}</h3>
<form @submit.prevent="handleSubmit">
<input
v-model="formData.name"
placeholder="姓名"
required
/>
<input
v-model="formData.email"
type="email"
placeholder="邮箱"
required
/>
<select v-model="formData.role">
<option value="user">普通用户</option>
<option value="admin">管理员</option>
<option value="guest">访客</option>
</select>
<button type="submit">{{ isEditing ? '更新' : '添加' }}</button>
</form>
</div>
</div>
</template>
<script>
import { ref, computed, watch } from 'vue'
export default {
name: 'UserManager',
setup() {
// 响应式数据
const users = ref([
{ id: 1, name: '张三', email: 'zhangsan@example.com', role: 'admin' },
{ id: 2, name: '李四', email: 'lisi@example.com', role: 'user' },
{ id: 3, name: '王五', email: 'wangwu@example.com', role: 'guest' }
])
const searchTerm = ref('')
const filterRole = ref('')
const currentPage = ref(1)
const isEditing = ref(false)
const editingUserId = ref(null)
// 表单数据
const formData = ref({
name: '',
email: '',
role: 'user'
})
// 计算属性
const filteredUsers = computed(() => {
let result = users.value
if (searchTerm.value) {
const term = searchTerm.value.toLowerCase()
result = result.filter(user =>
user.name.toLowerCase().includes(term) ||
user.email.toLowerCase().includes(term)
)
}
if (filterRole.value) {
result = result.filter(user => user.role === filterRole.value)
}
return result
})
const totalPages = computed(() => {
const itemsPerPage = 5
return Math.ceil(filteredUsers.value.length / itemsPerPage)
})
const paginatedUsers = computed(() => {
const itemsPerPage = 5
const start = (currentPage.value - 1) * itemsPerPage
const end = start + itemsPerPage
return filteredUsers.value.slice(start, end)
})
// 监听器
watch(currentPage, (newPage) => {
if (newPage > totalPages.value) {
currentPage.value = totalPages.value
}
})
// 方法
const editUser = (user) => {
isEditing.value = true
editingUserId.value = user.id
formData.value = { ...user }
}
const deleteUser = (userId) => {
if (confirm('确定要删除这个用户吗?')) {
users.value = users.value.filter(user => user.id !== userId)
}
}
const handleSubmit = () => {
if (isEditing.value) {
// 更新用户
const index = users.value.findIndex(user => user.id === editingUserId.value)
if (index !== -1) {
users.value[index] = { ...users.value[index], ...formData.value }
}
} else {
// 添加新用户
const newUser = {
id: Date.now(),
...formData.value
}
users.value.push(newUser)
}
// 重置表单
formData.value = { name: '', email: '', role: 'user' }
isEditing.value = false
editingUserId.value = null
}
return {
users,
searchTerm,
filterRole,
currentPage,
isEditing,
formData,
filteredUsers,
totalPages,
paginatedUsers,
editUser,
deleteUser,
handleSubmit
}
}
}
</script>
<style scoped>
.user-manager {
padding: 20px;
}
.search-section {
margin-bottom: 20px;
display: flex;
gap: 10px;
align-items: center;
}
.search-input, .role-filter {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.user-list {
margin-bottom: 20px;
}
.user-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
border: 1px solid #eee;
margin-bottom: 10px;
border-radius: 4px;
}
.user-info h3 {
margin: 0 0 10px 0;
}
.user-actions {
display: flex;
gap: 10px;
}
.btn-edit, .btn-delete {
padding: 5px 10px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btn-edit {
background-color: #007bff;
color: white;
}
.btn-delete {
background-color: #dc3545;
color: white;
}
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
margin-bottom: 20px;
}
.page-btn {
padding: 8px 16px;
border: 1px solid #ddd;
background-color: white;
cursor: pointer;
border-radius: 4px;
}
.page-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.add-user-form {
padding: 20px;
border: 1px solid #eee;
border-radius: 4px;
}
.add-user-form form {
display: flex;
flex-direction: column;
gap: 10px;
max-width: 300px;
}
.add-user-form input,
.add-user-form select {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.add-user-form button {
padding: 10px;
background-color: #28a745;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>
2. 表单验证组件
<template>
<div class="form-validator">
<form @submit.prevent="handleSubmit">
<div class="form-group">
<label>用户名:</label>
<input
v-model="formData.username"
type="text"
:class="{ 'error': errors.username }"
/>
<span v-if="errors.username" class="error-message">{{ errors.username }}</span>
</div>
<div class="form-group">
<label>邮箱:</label>
<input
v-model="formData.email"
type="email"
:class="{ 'error': errors.email }"
/>
<span v-if="errors.email" class="error-message">{{ errors.email }}</span>
</div>
<div class="form-group">
<label>密码:</label>
<input
v-model="formData.password"
type="password"
:class="{ 'error': errors.password }"
/>
<span v-if="errors.password" class="error-message">{{ errors.password }}</span>
</div>
<div class="form-group">
<label>确认密码:</label>
<input
v-model="formData.confirmPassword"
type="password"
:class="{ 'error': errors.confirmPassword }"
/>
<span v-if="errors.confirmPassword" class="error-message">{{ errors.confirmPassword }}</span>
</div>
<button type="submit" :disabled="!isFormValid">提交</button>
</form>
</div>
</template>
<script>
import { ref, reactive, computed } from 'vue'
export default {
name: 'FormValidator',
setup() {
// 表单数据
const formData = reactive({
username: '',
email: '',
password: '',
confirmPassword: ''
})
// 错误信息
const errors = reactive({})
// 验证规则
const validationRules = {
username: [
{
rule: (value) => value.length >= 3,
message: '用户名至少需要3个字符'
},
{
rule: (value) => /^[a-zA-Z0-9_]+$/.test(value),
message: '用户名只能包含字母、数字和下划线'
}
],
email: [
{
rule: (value) => value.includes('@'),
message: '请输入有效的邮箱地址'
},
{
rule: (value) => value.length > 5,
message: '邮箱地址过短'
}
],
password: [
{
rule: (value) => value.length >= 8,
message: '密码至少需要8个字符'
},
{
rule: (value) => /[A-Z]/.test(value),
message: '密码必须包含大写字母'
},
{
rule: (value) => /[0-9]/.test(value),
message: '密码必须包含数字'
}
],
confirmPassword: [
{
rule: (value) => value === formData.password,
message: '两次输入的密码不一致'
}
]
}
// 验证单个字段
const validateField = (fieldName) => {
const fieldRules = validationRules[fieldName]
if (!fieldRules) return
for (const rule of fieldRules) {
if (!rule.rule(formData[fieldName])) {
errors[fieldName] = rule.message
return
}
}
// 如果验证通过,清除错误
delete errors[fieldName]
}
// 验证所有字段
const validateAll = () => {
Object.keys(validationRules).forEach(fieldName => {
validateField(fieldName)
})
}
// 计算属性:表单是否有效
const isFormValid = computed(() => {
return Object.keys(errors).length === 0 &&
formData.username &&
formData.email &&
formData.password &&
formData.confirmPassword
})
// 监听表单数据变化并实时验证
Object.keys(formData).forEach(fieldName => {
const field = fieldName
watch(() => formData[field], () => {
validateField(field)
})
})
// 提交处理
const handleSubmit = () => {
validateAll()
if (isFormValid.value) {
console.log('表单提交成功:', formData)
alert('表单提交成功!')
} else {
console.log('表单验证失败')
alert('请检查表单输入')
}
}
return {
formData,
errors,
isFormValid,
handleSubmit
}
}
}
</script>
<style scoped>
.form-validator {
max-width: 400px;
margin: 20px auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.form-group input {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
.form-group input.error {
border-color: #dc3545;
}
.error-message {
color: #dc3545;
font-size: 12px;
margin-top: 5px;
display: block;
}
button {
width: 100%;
padding: 10px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
button:disabled {
background-color: #6c757d;
cursor: not-allowed;
}
</style>
高级特性与最佳实践
1. 组合式函数(Composable Functions)
组合式函数是 Composition API 的核心概念之一,它允许我们将可复用的逻辑封装成独立的函数。
// composables/useCounter.js
import { ref, computed } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const increment = () => {
count.value++
}
const decrement = () => {
count.value--
}
const reset = () => {
count.value = initialValue
}
const doubleCount = computed(() => count.value * 2)
return {
count,
increment,
decrement,
reset,
doubleCount
}
}
// composables/useApi.js
import { ref, reactive } from 'vue'
export function useApi() {
const loading = ref(false)
const error = ref(null)
const data = ref(null)
const fetchData = async (url) => {
try {
loading.value = true
error.value = null
const response = await fetch(url)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
data.value = await response.json()
} catch (err) {
error.value = err.message
console.error('API Error:', err)
} finally {
loading.value = false
}
}
return {
loading,
error,
data,
fetchData
}
}
2. 生命周期钩子
在 Composition API 中,生命周期钩子需要通过特定的函数来使用:
import {
onMounted,
onUpdated,
onUnmounted,
onBeforeMount,
onBeforeUpdate,
onBeforeUnmount
} from 'vue'
export default {
setup() {
const count = ref(0)
// 挂载前
onBeforeMount(() => {
console.log('组件即将挂载')
})
// 挂载后
onMounted(() => {
console.log('组件已挂载')
// 可以在这里执行 DOM 操作
const timer = setInterval(() => {
count.value++
}, 1000)
// 清理定时器
onUnmounted(() => {
clearInterval(timer)
})
})
// 更新前
onBeforeUpdate(() => {
console.log('组件即将更新')
})
// 更新后
onUpdated(() => {
console.log('组件已更新')
})
// 卸载前
onBeforeUnmount(() => {
console.log('组件即将卸载')
})
return {
count
}
}
}
3. 模板引用和 DOM 操作
<template>
<div>
<input ref="inputRef" v-model="message" />
<button @click="focusInput">聚焦输入框</button>
<p>{{ message }}</p>
</div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
setup() {
const message = ref('')
const inputRef = ref(null)
const focusInput = () => {
if (inputRef.value) {
inputRef.value.focus()
}
}
// 组件挂载后自动聚焦
onMounted(() => {
focusInput()
})
return {
message,
inputRef,
focusInput
}
}
}
</script>
4. 状态管理
// stores/userStore.js
import { reactive, readonly } from 'vue'
const state = reactive({
users: [],
currentUser: null,
loading: false,
error: null
})
export function useUserStore() {
const getUsers = async () => {
try {
state.loading = true
state.error = null
// 模拟 API 调用
const response = await fetch('/api/users')
const users = await response.json()
state.users = users
} catch (err) {
state.error = err.message
} finally {
state.loading = false
}
}
const addUser = (user) => {
state.users.push(user)
}
const removeUser = (userId) => {
state.users = state.users.filter(user => user.id !== userId)
}
const setCurrentUser = (user) => {
state.currentUser = user
}
return {
state: readonly(state),
getUsers,
addUser,
removeUser,
setCurrentUser
}
}
性能优化技巧
1. 使用 computed 缓存
import { ref, computed } from 'vue'
export default {
setup() {
const items = ref([])
const searchTerm = ref('')
// 高性能的计算属性
const filteredItems = computed(() => {
if (!searchTerm.value) return items.value
return items.value.filter(item =>
item.name.toLowerCase().includes(searchTerm.value.toLowerCase())
)
})
// 复杂计算使用缓存
const expensiveCalculation = computed(() => {
// 模拟复杂的计算
let result = 0
for (let i = 0; i < 1000000; i++) {
result += Math.sqrt(i)
}
return result
})
return {
items,
searchTerm,
filteredItems,
expensiveCalculation
}
}
}
2. 合理使用 watch
import { ref, watch } from 'vue'
export default {
setup() {
const searchQuery = ref('')
const debouncedSearch = ref('')
// 使用防抖优化
const debounce = (func, delay) => {
let timeoutId
return (...args) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => func.apply(this, args), delay)
}
}
const debouncedWatch = debounce((newValue) => {
debouncedSearch.value = newValue
}, 300)
watch(searchQuery, debouncedWatch)
return {
searchQuery,
debouncedSearch
}
}
}
与 Vue 2 的对比
选项式 API vs Composition API
// Vue 2 Options API
export default {
data() {
return {
count: 0,
message: 'Hello'
}
},
computed: {
doubledCount() {
return this.count * 2
}
},
methods: {
increment() {
this.count++
}
},
mounted() {
console.log('组件已挂载')
}
}
// Vue 3 Composition API
import { ref, computed, onMounted } from 'vue'
export default {
setup() {
const count = ref(0)
const message = ref('Hello')
const doubledCount = computed(() => count.value * 2)
const increment = () => {
count.value++
}
onMounted(() => {
console.log('组件已挂载')
})
return {
count,
message,
doubledCount,
increment
}
}
}
最佳实践总结
1. 组件结构优化
// 推荐的组件结构
import { ref, reactive, computed, watch, onMounted } from 'vue'
export default {
name: 'OptimizedComponent',
props: {
// 定义 props
},
setup(props, context) {
// 1. 响应式数据声明
const state = reactive({
data: [],
loading: false,
error: null
})
const localData = ref([])
// 2. 计算属性
const computedValue = computed(() => {
// 计算逻辑
})
// 3. 方法定义
const handleAction = () => {
// 处理逻辑
}
// 4. 生命周期钩子
onMounted(() => {
// 挂载后逻辑
})
// 5. 监听器
watch(() => props.someProp, (newValue, oldValue) => {
// 监听逻辑
})
// 6. 返回需要暴露给模板的属性
return {
state,

评论 (0)