引言
Vue 3的发布带来了革命性的Composition API,它为开发者提供了更加灵活和强大的组件开发方式。相比于Vue 2的选项式API,Composition API将逻辑组织方式从"选项"转向了"组合",使得代码更加模块化、可复用和易于维护。本文将深入探讨Composition API的核心特性,并分享在实际开发中构建可复用组件的最佳实践。
Composition API核心概念
什么是Composition API
Composition API是Vue 3中引入的一种新的组件逻辑组织方式。它允许开发者将组件的逻辑按照功能进行组合,而不是按照选项类型进行组织。这种设计模式使得组件更加灵活,逻辑复用变得更加容易。
// Vue 2 选项式API
export default {
data() {
return {
count: 0,
name: ''
}
},
methods: {
increment() {
this.count++
}
},
computed: {
reversedName() {
return this.name.split('').reverse().join('')
}
}
}
// Vue 3 Composition API
import { ref, computed } from 'vue'
export default {
setup() {
const count = ref(0)
const name = ref('')
const increment = () => {
count.value++
}
const reversedName = computed(() => {
return name.value.split('').reverse().join('')
})
return {
count,
name,
increment,
reversedName
}
}
}
setup函数的作用
setup函数是Composition API的核心,它在组件实例创建之前执行,接收两个参数:props和context。
export default {
props: ['title'],
setup(props, context) {
// props是响应式对象
console.log(props.title)
// context包含组件上下文信息
console.log(context.attrs)
console.log(context.slots)
console.log(context.emit)
// 返回需要在模板中使用的数据和方法
return {
// 数据和方法
}
}
}
组件封装与逻辑复用
基础逻辑复用
在Vue 2中,逻辑复用主要通过mixins实现,但mixins存在命名冲突、作用域不清晰等问题。Composition API通过函数的方式实现了更优雅的逻辑复用。
// 用户信息复用逻辑
import { ref, onMounted } from 'vue'
export function useUserInfo() {
const userInfo = ref(null)
const loading = ref(false)
const error = ref(null)
const fetchUserInfo = async (userId) => {
loading.value = true
error.value = null
try {
const response = await fetch(`/api/users/${userId}`)
userInfo.value = await response.json()
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
return {
userInfo,
loading,
error,
fetchUserInfo
}
}
// 在组件中使用
import { useUserInfo } from '@/composables/useUserInfo'
export default {
setup() {
const { userInfo, loading, error, fetchUserInfo } = useUserInfo()
onMounted(() => {
fetchUserInfo(123)
})
return {
userInfo,
loading,
error
}
}
}
复杂逻辑封装
对于更复杂的业务逻辑,我们可以创建更加专业的组合函数:
// 表单验证组合函数
import { ref, reactive, computed } from 'vue'
export function useFormValidation(initialValues = {}) {
const form = reactive({ ...initialValues })
const errors = ref({})
const isValid = computed(() => Object.keys(errors.value).length === 0)
const validateField = (fieldName, value) => {
// 简单的验证规则
const rules = {
email: (val) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val),
required: (val) => val !== null && val !== undefined && val !== '',
minLength: (val, min) => val.length >= min
}
// 这里可以扩展更复杂的验证逻辑
return true
}
const validateForm = () => {
errors.value = {}
// 实现表单验证逻辑
return isValid.value
}
const setFieldValue = (field, value) => {
form[field] = value
// 可以在这里添加实时验证
if (validateField(field, value)) {
delete errors.value[field]
} else {
errors.value[field] = 'Invalid value'
}
}
return {
form,
errors,
isValid,
validateForm,
setFieldValue
}
}
// 使用示例
export default {
setup() {
const { form, errors, isValid, validateForm, setFieldValue } = useFormValidation({
email: '',
password: ''
})
const handleSubmit = () => {
if (validateForm()) {
// 提交表单
}
}
return {
form,
errors,
isValid,
handleSubmit,
setFieldValue
}
}
}
状态管理与响应式系统
深入响应式系统
Composition API的核心是响应式系统,理解其工作机制对于构建高效组件至关重要。
import { ref, reactive, computed, watch, watchEffect } from 'vue'
// ref vs reactive
const count = ref(0) // 创建响应式引用
const user = reactive({ name: 'John', age: 30 }) // 创建响应式对象
// 访问值时需要使用.value
console.log(count.value) // 0
count.value = 1 // 修改值
// 计算属性
const doubled = computed(() => count.value * 2)
const fullName = computed({
get: () => `${user.name} ${user.age}`,
set: (value) => {
const names = value.split(' ')
user.name = names[0]
user.age = names[1]
}
})
// 监听器
watch(count, (newVal, oldVal) => {
console.log(`count changed from ${oldVal} to ${newVal}`)
})
// watchEffect会自动追踪依赖
watchEffect(() => {
console.log(`Name: ${user.name}, Count: ${count.value}`)
})
// 深度监听
const deepObj = reactive({
nested: {
value: 1
}
})
watch(deepObj, (newVal) => {
console.log('Object changed:', newVal)
}, { deep: true })
全局状态管理
在大型应用中,我们需要更高级的状态管理方案:
// 全局状态管理组合函数
import { reactive, readonly } from 'vue'
// 创建全局状态存储
export const useGlobalStore = () => {
const state = reactive({
user: null,
theme: 'light',
language: 'zh-CN'
})
const setUser = (user) => {
state.user = user
}
const setTheme = (theme) => {
state.theme = theme
}
const setLanguage = (language) => {
state.language = language
}
return {
state: readonly(state),
setUser,
setTheme,
setLanguage
}
}
// 在应用中使用
import { useGlobalStore } from '@/stores/globalStore'
export default {
setup() {
const { state, setUser, setTheme } = useGlobalStore()
// 使用状态
const currentUser = computed(() => state.user)
return {
currentUser,
setTheme
}
}
}
高级组件模式
自定义指令与组件组合
Composition API使得自定义指令的编写更加直观:
// 防抖指令
import { onMounted, onUnmounted } from 'vue'
export const vDebounce = {
mounted(el, binding, vnode) {
const { handler, delay = 300 } = binding.value
let timeoutId
const debouncedHandler = (...args) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => handler(...args), delay)
}
el.addEventListener('input', debouncedHandler)
// 清理
onUnmounted(() => {
clearTimeout(timeoutId)
el.removeEventListener('input', debouncedHandler)
})
}
}
// 使用自定义指令
export default {
setup() {
const handleSearch = (value) => {
console.log('Search:', value)
}
return {
handleSearch
}
}
}
动态组件与异步加载
import { ref, defineAsyncComponent } from 'vue'
export default {
setup() {
const currentComponent = ref(null)
const loading = ref(false)
// 异步组件加载
const loadComponent = async (componentName) => {
loading.value = true
try {
const component = await import(`@/components/${componentName}.vue`)
currentComponent.value = component.default
} catch (error) {
console.error('Failed to load component:', error)
} finally {
loading.value = false
}
}
// 动态组件
const DynamicComponent = defineAsyncComponent({
loader: () => import('@/components/MyComponent.vue'),
loadingComponent: () => import('@/components/Loading.vue'),
errorComponent: () => import('@/components/Error.vue'),
delay: 200,
timeout: 3000
})
return {
currentComponent,
loading,
loadComponent,
DynamicComponent
}
}
}
性能优化技巧
计算属性优化
import { computed, shallowRef, triggerRef } from 'vue'
// 避免不必要的计算
export function useOptimizedComputed() {
const data = ref([])
const expensiveValue = computed(() => {
// 只有当data变化时才重新计算
return data.value.reduce((acc, item) => acc + item.value, 0)
})
// 对于大型对象,可以使用shallowRef
const shallowData = shallowRef({})
// 手动触发更新
const refreshData = () => {
triggerRef(shallowData)
}
return {
expensiveValue,
refreshData
}
}
组件缓存与渲染优化
import { keepAlive, onActivated, onDeactivated } from 'vue'
// 缓存组件
export default {
name: 'CachedComponent',
setup() {
const data = ref([])
// 组件激活时
onActivated(() => {
console.log('Component activated')
// 重新加载数据
})
// 组件失活时
onDeactivated(() => {
console.log('Component deactivated')
// 清理资源
})
return {
data
}
}
}
实际应用案例
数据表格组件
<template>
<div class="data-table">
<div class="table-header">
<button @click="refreshData">刷新</button>
<input v-model="searchTerm" placeholder="搜索..." />
</div>
<table>
<thead>
<tr>
<th v-for="column in columns" :key="column.key">
{{ column.title }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="row in filteredData" :key="row.id">
<td v-for="column in columns" :key="column.key">
{{ formatValue(row, column) }}
</td>
</tr>
</tbody>
</table>
<div class="pagination">
<button @click="prevPage" :disabled="currentPage === 1">上一页</button>
<span>{{ currentPage }} / {{ totalPages }}</span>
<button @click="nextPage" :disabled="currentPage === totalPages">下一页</button>
</div>
</div>
</template>
<script>
import { ref, computed, watch } from 'vue'
export default {
props: {
columns: {
type: Array,
required: true
},
data: {
type: Array,
required: true
}
},
setup(props) {
const currentPage = ref(1)
const pageSize = ref(10)
const searchTerm = ref('')
const filteredData = computed(() => {
if (!searchTerm.value) return props.data
return props.data.filter(item =>
Object.values(item).some(value =>
value.toString().toLowerCase().includes(searchTerm.value.toLowerCase())
)
)
})
const totalPages = computed(() => {
return Math.ceil(filteredData.value.length / pageSize.value)
})
const paginatedData = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
const end = start + pageSize.value
return filteredData.value.slice(start, end)
})
const refreshData = () => {
currentPage.value = 1
}
const nextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++
}
}
const prevPage = () => {
if (currentPage.value > 1) {
currentPage.value--
}
}
const formatValue = (row, column) => {
if (column.formatter) {
return column.formatter(row[column.key], row)
}
return row[column.key]
}
return {
currentPage,
searchTerm,
filteredData: paginatedData,
totalPages,
refreshData,
nextPage,
prevPage,
formatValue
}
}
}
</script>
表单验证组件
<template>
<form @submit.prevent="handleSubmit">
<div class="form-group" v-for="field in fields" :key="field.name">
<label>{{ field.label }}</label>
<input
v-model="form[field.name]"
:type="field.type"
:required="field.required"
:pattern="field.pattern"
/>
<span class="error" v-if="errors[field.name]">
{{ errors[field.name] }}
</span>
</div>
<button type="submit" :disabled="!isValid">提交</button>
</form>
</template>
<script>
import { ref, reactive, computed } from 'vue'
export default {
props: {
fields: {
type: Array,
required: true
}
},
setup(props, { emit }) {
const form = reactive({})
const errors = ref({})
// 初始化表单
props.fields.forEach(field => {
form[field.name] = ''
})
const validateField = (fieldName, value) => {
const field = props.fields.find(f => f.name === fieldName)
if (!field) return true
// 必填验证
if (field.required && !value) {
errors.value[fieldName] = '此字段为必填项'
return false
}
// 格式验证
if (field.pattern && value && !field.pattern.test(value)) {
errors.value[fieldName] = field.errorMessage || '格式不正确'
return false
}
delete errors.value[fieldName]
return true
}
const validateForm = () => {
let isValid = true
errors.value = {}
Object.keys(form).forEach(fieldName => {
if (!validateField(fieldName, form[fieldName])) {
isValid = false
}
})
return isValid
}
const handleSubmit = () => {
if (validateForm()) {
emit('submit', form)
}
}
const isValid = computed(() => {
return Object.keys(errors.value).length === 0
})
// 监听表单变化
Object.keys(form).forEach(fieldName => {
watch(() => form[fieldName], (newValue) => {
validateField(fieldName, newValue)
})
})
return {
form,
errors,
isValid,
handleSubmit
}
}
}
</script>
最佳实践总结
代码组织原则
- 按功能分组:将相关的逻辑组织在一起,避免功能分散
- 单一职责:每个组合函数应该只负责一个特定的业务逻辑
- 可复用性:设计时要考虑组件的通用性和可复用性
// 好的实践
export function useApi() {
// API相关逻辑
}
export function useAuth() {
// 认证相关逻辑
}
export function useStorage() {
// 存储相关逻辑
}
// 避免这样
export function useUser() {
// 用户相关逻辑
// API调用逻辑
// 认证逻辑
// 存储逻辑
}
性能优化建议
- 合理使用计算属性:避免在计算属性中进行复杂计算
- 避免不必要的监听器:及时清理监听器
- 组件懒加载:对于大型组件使用动态导入
开发工具支持
// 使用Vue DevTools
import { onMounted, onUnmounted } from 'vue'
export default {
setup() {
const startTime = performance.now()
onMounted(() => {
console.log('Component mounted in', performance.now() - startTime, 'ms')
})
onUnmounted(() => {
console.log('Component unmounted')
})
return {}
}
}
结论
Vue 3的Composition API为前端开发带来了革命性的变化,它不仅提供了更灵活的组件组织方式,还大大增强了代码的可复用性和可维护性。通过合理运用组合函数、响应式系统和高级组件模式,我们可以构建出更加优雅和高效的Vue应用。
在实际开发中,建议开发者:
- 深入理解响应式系统的原理
- 善于将通用逻辑封装成组合函数
- 合理使用计算属性和监听器
- 注重性能优化和用户体验
随着Vue生态的不断发展,Composition API必将在未来的前端开发中发挥更加重要的作用。掌握这些高级应用技巧,将帮助开发者构建出更加现代化、可维护的Vue应用。

评论 (0)