Vue 3组合式API完全指南
作为一名从Vue 2一路走来的开发者,我必须承认,刚开始接触Vue 3的组合式API时,我确实有点懵。选项式API那种直观的方式用了那么多年,突然要学习一套新的语法,说实话心里是有点抵触的。但用过一段时间后,我发现组合式API真的香!今天就以过来人的身份,手把手带你玩转Vue 3的组合式API。
一、什么是组合式API?
从选项式到组合式
在Vue 2中,我们习惯于这样的组织方式:
export default { data() { return { count: 0, name: '张三' } }, computed: { doubleCount() { return this.count * 2 } }, methods: { increment() { this.count++ } }, watch: { count(newVal, oldVal) { console.log(`count从${oldVal}变成了${newVal}`) } } }
|
而在Vue 3的组合式API中,代码组织方式变成了:
import { ref, computed, watch } from 'vue'
export default { setup() { const count = ref(0) const name = ref('张三') const doubleCount = computed(() => count.value * 2) const increment = () => { count.value++ } watch(count, (newVal, oldVal) => { console.log(`count从${oldVal}变成了${newVal}`) }) return { count, name, doubleCount, increment } } }
|
为什么需要组合式API?
- 更好的逻辑复用:在选项式API中,混入逻辑经常导致”模板地狱”,组合式API让逻辑复用变得简单
- 更好的TypeScript支持:组合式API与TypeScript是天作之合
- 更灵活的代码组织:相关逻辑可以就近放置,而不是分散在不同的选项中
- 更好的逻辑拆分:大型组件的逻辑更容易拆分和维护
二、基础语法详解
1. setup函数
setup是组合式API的核心,它是组件初始化的地方。
export default { setup() { const message = ref('Hello Vue 3!') const greet = () => { alert(message.value) } return { message, greet } } }
|
2. ref和reactive
ref - 用于基本类型数据
import { ref } from 'vue'
const count = ref(0) const message = ref('Hello') const isActive = ref(true)
console.log(count.value) count.value++ console.log(count.value)
|
reactive - 用于对象和数组
import { reactive } from 'vue'
const state = reactive({ count: 0, user: { name: '张三', age: 25 }, hobbies: ['编程', '阅读', '运动'] })
console.log(state.count) state.count++ console.log(state.count)
console.log(state.user.name) state.user.name = '李四' console.log(state.user.name)
|
3. 何时使用ref,何时使用reactive?
const count = ref(0) const count = reactive(0)
const user = reactive({ name: '张三' }) const user = ref({ name: '张三' })
const list = reactive([1, 2, 3]) const list = ref([1, 2, 3])
const data = ref([])
data.value = [1, 2, 3, 4]
const data = reactive([])
data = [1, 2, 3]
|
三、计算属性和监听器
computed - 计算属性
import { ref, computed } from 'vue'
export default { setup() { const firstName = ref('张') const lastName = ref('三') const fullName = computed(() => { return `${firstName.value}${lastName.value}` }) const reactiveFullName = computed({ get: () => `${firstName.value}${lastName.value}`, set: (newValue) => { const names = newValue.split(' ') firstName.value = names[0] || '' lastName.value = names[1] || '' } }) return { firstName, lastName, fullName, reactiveFullName } } }
|
watch - 监听数据变化
监听ref
import { ref, watch } from 'vue'
export default { setup() { const count = ref(0) const name = ref('张三') watch(count, (newValue, oldValue) => { console.log(`count从${oldValue}变成了${newValue}`) }) watch([count, name], ([newCount, newName], [oldCount, oldName]) => { console.log(`count从${oldCount}变成了${newCount}`) console.log(`name从${oldName}变成了${newName}`) }) watch(count, (newValue, oldValue) => { console.log(`初始值:${newValue}`) }, { immediate: true }) return { count, name } } }
|
监听reactive
import { reactive, watch } from 'vue'
export default { setup() { const user = reactive({ name: '张三', age: 25 }) watch(user, (newValue, oldValue) => { console.log('用户信息发生了变化', newValue) }) watch( () => user.name, (newName, oldName) => { console.log(`姓名从${oldName}变成了${newName}`) } ) watch( () => user, (newValue) => { console.log('整个用户对象变化了', newValue) }, { deep: true } ) return { user } } }
|
watchEffect - 自动追踪依赖
import { ref, watchEffect } from 'vue'
export default { setup() { const count = ref(0) const double = ref(0) watchEffect(() => { double.value = count.value * 2 console.log(`count是${count.value},double是${double.value}`) }) count.value++ const stop = watchEffect(() => { console.log('watchEffect运行了') }) return { count, double } } }
|
四、生命周期钩子
组合式API中的生命周期钩子都在on前缀下:
import { ref, onMounted, onUpdated, onUnmounted } from 'vue'
export default { setup() { const count = ref(0) onMounted(() => { console.log('组件挂载了') }) onUpdated(() => { console.log('组件更新了') }) onUnmounted(() => { console.log('组件即将卸载') }) const increment = () => { count.value++ } return { count, increment } } }
|
常用生命周期钩子
| 选项式API | 组合式API | 执行时机 |
|---|
| beforeCreate | setup | 组件实例创建之前 |
| created | setup | 组件实例创建之后 |
| beforeMount | onBeforeMount | 组件挂载到DOM之前 |
| mounted | onMounted | 组件挂载到DOM之后 |
| beforeUpdate | onBeforeUpdate | 组件更新之前 |
| updated | onUpdated | 组件更新之后 |
| beforeUnmount | onBeforeUnmount | 组件卸载之前 |
| unmounted | onUnmounted | 组件卸载之后 |
五、模板引用
ref用于DOM元素
<template> <div> <input ref="inputRef" placeholder="请输入内容" /> <button @click="focusInput">聚焦输入框</button> </div> </template>
<script> import { ref, onMounted } from 'vue'
export default { setup() { // 创建ref来引用DOM元素 const inputRef = ref(null) const focusInput = () => { inputRef.value?.focus() } onMounted(() => { // 组件挂载后,inputRef.value指向DOM元素 console.log('输入框元素:', inputRef.value) }) return { inputRef, focusInput } } } </script>
|
ref用于子组件
<!-- Parent.vue --> <template> <div> <Child ref="childRef" /> <button @click="callChildMethod">调用子组件方法</button> </div> </template>
<script> import { ref } from 'vue' import Child from './Child.vue'
export default { components: { Child }, setup() { const childRef = ref(null) const callChildMethod = () => { // 访问子组件的公共属性和方法 childRef.value.childMethod() console.log('子组件数据:', childRef.value.someData) } return { childRef, callChildMethod } } } </script>
|
六、组件通信
props传递
<!-- Parent.vue --> <template> <Child :user="user" @update="handleUpdate" /> </template>
<script> import { ref } from 'vue' import Child from './Child.vue'
export default { components: { Child }, setup() { const user = ref({ name: '张三', age: 25 }) const handleUpdate = (newName) => { user.value.name = newName } return { user, handleUpdate } } } </script>
|
<!-- Child.vue --> <template> <div> <p>姓名: {{ user.name }}</p> <p>年龄: {{ user.age }}</p> <button @click="changeName">修改姓名</button> </div> </template>
<script> import { defineProps, defineEmits } from 'vue'
export default { // 接收props props: { user: { type: Object, required: true, validator: (value) => { return value && typeof value.name === 'string' } } }, // 或者使用defineProps // const props = defineProps(['user']) // 定义事件 emits: ['update'], // 或者使用defineEmits // const emit = defineEmits(['update']) setup(props, { emit }) { const changeName = () => { // 触发事件 emit('update', '李四') } return { changeName } } } </script>
|
provide/inject
<!-- 祖组件 --> <template> <div> <h1>祖组件</h1> <Child /> </div> </template>
<script> import { provide, ref } from 'vue' import Child from './Child.vue'
export default { components: { Child }, setup() { // 提供响应式数据 const theme = ref('light') const user = ref({ name: '张三', age: 25 }) // 提供方法 const toggleTheme = () => { theme.value = theme.value === 'light' ? 'dark' : 'light' } // 向后代组件提供数据 provide('theme', theme) provide('user', user) provide('toggleTheme', toggleTheme) return {} } } </script>
|
<!-- 孙组件 --> <template> <div :class="theme"> <h2>孙组件</h2> <p>主题: {{ theme }}</p> <p>用户名: {{ user.name }}</p> <button @click="toggleTheme">切换主题</button> </div> </template>
<script> import { inject } from 'vue'
export default { setup() { // 注入数据 const theme = inject('theme') const user = inject('user') const toggleTheme = inject('toggleTheme') // 可以设置默认值 // const theme = inject('theme', 'light') // 对于必需的依赖,可以检查是否提供 // const theme = inject('theme') // if (!theme) { // throw new Error('Theme is required!') // } return { theme, user, toggleTheme } } } </script>
|
mitt事件总线
import mitt from 'mitt' export const emitter = mitt()
import { emitter } from './eventBus' emitter.emit('custom-event', data)
import { emitter } from './eventBus' emitter.on('custom-event', (data) => { console.log('收到事件:', data) })
|
七、响应式进阶
shallowRef和shallowReactive
import { ref, reactive, shallowRef, shallowReactive } from 'vue'
const user = reactive({ name: '张三', address: { city: '北京', district: '海淀' } })
const shallowUser = shallowReactive({ name: '张三', address: { city: '北京', district: '海淀' } })
const count = ref(0)
const shallowCount = shallowRef({ value: 0 })
|
readonly和shallowReadonly
import { ref, readonly, shallowReadonly } from 'vue'
const original = ref({ count: 0 }) const readonlyData = readonly(original)
const shallowReadonlyData = shallowReadonly({ count: 0, nested: { value: 1 } })
|
toRef和toRefs
import { reactive, toRef, toRefs } from 'vue'
const state = reactive({ name: '张三', age: 25, address: '北京' })
const nameRef = toRef(state, 'name') nameRef.value = '李四' console.log(state.name)
const stateRefs = toRefs(state) console.log(stateRefs.name.value) console.log(stateRefs.age.value)
const { name, age } = toRefs(state)
|
isRef和isReactive
import { ref, reactive, isRef, isReactive } from 'vue'
const count = ref(0) const state = reactive({ name: '张三' })
console.log(isRef(count)) console.log(isRef(state)) console.log(isReactive(count)) console.log(isReactive(state))
|
八、自定义Hooks
这是组合式API最强大的功能,让我们能够复用逻辑。
1. 创建简单的Hook
import { ref } from 'vue'
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 } }
|
<template> <div> <p>Count: {{ count }}</p> <button @click="increment">+</button> <button @click="decrement">-</button> <button @click="reset">Reset</button> </div> </template>
<script> import { useCounter } from './useCounter'
export default { setup() { const { count, increment, decrement, reset } = useCounter(10) return { count, increment, decrement, reset } } } </script>
|
2. 复杂的Hook - API请求
import { ref, onMounted, watch } from 'vue'
export function useFetch(url, options = {}) { const data = ref(null) const error = ref(null) const loading = ref(false) const fetchData = async () => { loading.value = true error.value = null try { const response = await fetch(url, options) if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`) } data.value = await response.json() } catch (err) { error.value = err } finally { loading.value = false } } onMounted(() => { fetchData() }) watch(() => url, () => { fetchData() }) return { data, error, loading, refetch: fetchData } }
|
<template> <div> <div v-if="loading">加载中...</div> <div v-else-if="error">错误: {{ error.message }}</div> <div v-else> <pre>{{ data }}</pre> </div> <button @click="refetch" :disabled="loading">重新加载</button> </div> </template>
<script> import { useFetch } from './useFetch'
export default { setup() { const { data, error, loading, refetch } = useFetch('https://api.example.com/data') return { data, error, loading, refetch } } } </script>
|
3. 完整的Hook - 表单验证
import { ref, reactive, onMounted } from 'vue'
export function useForm(rules) { const formData = reactive({}) const errors = reactive({}) const touched = reactive({}) Object.keys(rules).forEach(key => { formData[key] = '' touched[key] = false }) const validate = (field) => { const rule = rules[field] const value = formData[field] touched[field] = true errors[field] = null if (!rule) return true if (rule.required && (!value || value.trim() === '')) { errors[field] = rule.message || `${field}不能为空` return false } if (rule.min && value.length < rule.min) { errors[field] = rule.message || `${field}至少需要${rule.min}个字符` return false } if (rule.max && value.length > rule.max) { errors[field] = rule.message || `${field}不能超过${rule.max}个字符` return false } if (rule.pattern && !rule.pattern.test(value)) { errors[field] = rule.message || `${field}格式不正确` return false } return true } const validateForm = () => { let isValid = true Object.keys(rules).forEach(key => { if (!validate(key)) { isValid = false } }) return isValid } const handleSubmit = (callback) => { const isValid = validateForm() if (isValid && callback) { callback(formData) } return isValid } const resetForm = () => { Object.keys(rules).forEach(key => { formData[key] = '' errors[key] = null touched[key] = false }) } return { formData, errors, touched, validate, validateForm, handleSubmit, resetForm } }
|
<template> <form @submit.prevent="submitForm"> <div> <label>用户名:</label> <input v-model="formData.username" @blur="validate('username')" /> <div v-if="errors.username" class="error">{{ errors.username }}</div> </div> <div> <label>邮箱:</label> <input v-model="formData.email" @blur="validate('email')" /> <div v-if="errors.email" class="error">{{ errors.email }}</div> </div> <div> <label>密码:</label> <input v-model="formData.password" type="password" @blur="validate('password')" /> <div v-if="errors.password" class="error">{{ errors.password }}</div> </div> <button type="submit">提交</button> </form> </template>
<script> import { useForm } from './useForm'
export default { setup() { const rules = { username: { required: true, min: 3, max: 20, pattern: /^[a-zA-Z0-9_]+$/, message: '用户名必须是3-20个字母、数字或下划线' }, email: { required: true, pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, message: '邮箱格式不正确' }, password: { required: true, min: 6, message: '密码至少需要6个字符' } } const { formData, errors, validate, handleSubmit, resetForm } = useForm(rules) const submitForm = () => { handleSubmit((data) => { console.log('表单提交成功:', data) // 这里可以添加API调用 alert('提交成功!') resetForm() }) } return { formData, errors, validate, submitForm } } } </script>
|
九、实战案例
案例1:待办事项应用
<template> <div class="todo-app"> <h1>待办事项</h1> <div class="add-todo"> <input v-model="newTodo" @keyup.enter="addTodo" placeholder="添加新的待办事项" /> <button @click="addTodo">添加</button> </div> <div class="filters"> <button v-for="filter in filters" :key="filter" :class="{ active: currentFilter === filter }" @click="currentFilter = filter" > {{ filter }} </button> </div> <ul class="todo-list"> <li v-for="todo in filteredTodos" :key="todo.id" :class="{ completed: todo.completed }" > <input type="checkbox" v-model="todo.completed" @change="saveTodos" /> <span>{{ todo.text }}</span> <button class="delete" @click="deleteTodo(todo.id)">删除</button> </li> </ul> <div class="stats"> <p>总计: {{ todos.length }}</p> <p>已完成: {{ completedCount }}</p> <p>未完成: {{ remainingCount }}</p> </div> </div> </template>
<script> import { ref, computed, watch, onMounted } from 'vue'
export default { setup() { // 状态管理 const todos = ref([]) const newTodo = ref('') const currentFilter = ref('全部') const filters = ['全部', '进行中', '已完成'] // 计算属性 const filteredTodos = computed(() => { switch (currentFilter.value) { case '进行中': return todos.value.filter(todo => !todo.completed) case '已完成': return todos.value.filter(todo => todo.completed) default: return todos.value } }) const completedCount = computed(() => { return todos.value.filter(todo => todo.completed).length }) const remainingCount = computed(() => { return todos.value.filter(todo => !todo.completed).length }) // 方法 const addTodo = () => { if (newTodo.value.trim()) { todos.value.push({ id: Date.now(), text: newTodo.value.trim(), completed: false }) newTodo.value = '' saveTodos() } } const deleteTodo = (id) => { todos.value = todos.value.filter(todo => todo.id !== id) saveTodos() } const saveTodos = () => { localStorage.setItem('todos', JSON.stringify(todos.value)) } // 从localStorage加载数据 const loadTodos = () => { const savedTodos = localStorage.getItem('todos') if (savedTodos) { todos.value = JSON.parse(savedTodos) } } // 生命周期 onMounted(() => { loadTodos() }) return { todos, newTodo, currentFilter, filters, filteredTodos, completedCount, remainingCount, addTodo, deleteTodo } } } </script>
<style scoped> .todo-app { max-width: 600px; margin: 0 auto; padding: 20px; }
.add-todo { display: flex; margin-bottom: 20px; }
.add-todo input { flex: 1; padding: 8px; margin-right: 10px; }
.filters { display: flex; gap: 10px; margin-bottom: 20px; }
.filters button { padding: 5px 10px; cursor: pointer; }
.filters button.active { background: #007bff; color: white; }
.todo-list { list-style: none; padding: 0; }
.todo-list li { display: flex; align-items: center; padding: 10px; border-bottom: 1px solid #eee; }
.todo-list li.completed span { text-decoration: line-through; color: #999; }
.delete { margin-left: auto; background: #dc3545; color: white; border: none; padding: 5px 10px; cursor: pointer; }
.stats { margin-top: 20px; padding: 10px; background: #f8f9fa; border-radius: 5px; }
.error { color: red; font-size: 0.9em; margin-top: 5px; } </style>
|
案例2:电商购物车
<template> <div class="shopping-cart"> <h1>购物车</h1> <div class="cart-items"> <div v-for="item in cartItems" :key="item.id" class="cart-item"> <img :src="item.image" :alt="item.name" class="item-image" /> <div class="item-details"> <h3>{{ item.name }}</h3> <p class="price">¥{{ item.price }}</p> <div class="quantity-controls"> <button @click="decreaseQuantity(item)" :disabled="item.quantity <= 1">-</button> <span>{{ item.quantity }}</span> <button @click="increaseQuantity(item)">+</button> </div> </div> <button class="remove" @click="removeFromCart(item.id)">删除</button> </div> </div> <div class="cart-summary"> <div class="summary-item"> <span>商品总数:</span> <span>{{ totalItems }}件</span> </div> <div class="summary-item"> <span>总价:</span> <span class="total-price">¥{{ totalPrice }}</span> </div> <button class="checkout" @click="checkout" :disabled="cartItems.length === 0"> 结算 </button> </div> </div> </template>
<script> import { ref, computed } from 'vue'
export default { setup() { // 模拟商品数据 const products = ref([ { id: 1, name: 'iPhone 13', price: 5999, image: 'https://example.com/iphone13.jpg' }, { id: 2, name: 'MacBook Pro', price: 12999, image: 'https://example.com/macbook.jpg' }, { id: 3, name: 'AirPods Pro', price: 1999, image: 'https://example.com/airpods.jpg' } ]) // 购物车状态 const cartItems = ref([ { id: 1, name: 'iPhone 13', price: 5999, quantity: 2, image: 'https://example.com/iphone13.jpg' }, { id: 3, name: 'AirPods Pro', price: 1999, quantity: 1, image: 'https://example.com/airpods.jpg' } ]) // 计算属性 const totalItems = computed(() => { return cartItems.value.reduce((total, item) => total + item.quantity, 0) }) const totalPrice = computed(() => { return cartItems.value.reduce((total, item) => total + item.price * item.quantity, 0) }) // 方法 const increaseQuantity = (item) => { const cartItem = cartItems.value.find(i => i.id === item.id) if (cartItem) { cartItem.quantity++ } } const decreaseQuantity = (item) => { const cartItem = cartItems.value.find(i => i.id === item.id) if (cartItem && cartItem.quantity > 1) { cartItem.quantity-- } } const removeFromCart = (productId) => { cartItems.value = cartItems.value.filter(item => item.id !== productId) } const checkout = () => { // 这里可以添加结算逻辑 alert(`结算成功!总金额: ¥${totalPrice.value}`) cartItems.value = [] } return { products, cartItems, totalItems, totalPrice, increaseQuantity, decreaseQuantity, removeFromCart, checkout } } } </script>
<style scoped> .shopping-cart { max-width: 800px; margin: 0 auto; padding: 20px; }
.cart-items { margin-bottom: 30px; }
.cart-item { display: flex; align-items: center; padding: 15px; border-bottom: 1px solid #eee; margin-bottom: 15px; }
.item-image { width: 80px; height: 80px; object-fit: cover; margin-right: 15px; }
.item-details { flex: 1; }
.item-details h3 { margin: 0 0 5px 0; }
.price { color: #e44d26; font-weight: bold; margin: 5px 0; }
.quantity-controls { display: flex; align-items: center; gap: 10px; margin-top: 10px; }
.quantity-controls button { background: #007bff; color: white; border: none; width: 30px; height: 30px; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; }
.quantity-controls button:disabled { background: #ccc; cursor: not-allowed; }
.remove { background: #dc3545; color: white; border: none; padding: 8px 15px; border-radius: 5px; cursor: pointer; margin-left: 15px; }
.cart-summary { border-top: 2px solid #eee; padding-top: 20px; }
.summary-item { display: flex; justify-content: space-between; margin-bottom: 10px; font-size: 1.1em; }
.total-price { color: #e44d26; font-weight: bold; font-size: 1.3em; }
.checkout { background: #28a745; color: white; border: none; padding: 12px 30px; border-radius: 5px; cursor: pointer; font-size: 1.1em; width: 100%; margin-top: 15px; }
.checkout:disabled { background: #ccc; cursor: not-allowed; } </style>
|
十、最佳实践
1. 代码组织
setup() { const user = ref(null) const loading = ref(false) const isAuthenticated = computed(() => !!user.value) const login = async () => { loading.value = true try { } finally { loading.value = false } } onMounted(() => { }) return { user, loading, isAuthenticated, login } }
|
2. 性能优化
const bigList = shallowRef(Array(10000).fill(null).map((_, i) => ({ id: i, name: `Item ${i}` })))
const expensiveData = computed(() => { })
const stopWatch = watchEffect(() => { return () => { } })
|
3. TypeScript支持
interface User { id: number name: string email: string }
interface Todo { id: number text: string completed: boolean }
import { ref, computed } from 'vue'
export default { setup() { const user = ref<User | null>(null) const todos = ref<Todo[]>([]) const completedTodos = computed(() => todos.value.filter(todo => todo.completed) ) return { user, todos, completedTodos } } }
|
4. 测试
import { ref } from 'vue' import { mount } from '@vue/test-utils' import TodoList from './TodoList.vue'
describe('TodoList', () => { it('adds a new todo', async () => { const wrapper = mount(TodoList) await wrapper.find('input').setValue('New Todo') await wrapper.find('button').trigger('click') expect(wrapper.findAll('li')).toHaveLength(1) expect(wrapper.text()).toContain('New Todo') }) it('marks todo as completed', async () => { const wrapper = mount(TodoList) await wrapper.find('button').trigger('click') const checkbox = wrapper.find('input[type="checkbox"]') await checkbox.trigger('click') expect(wrapper.find('li').classes()).toContain('completed') }) })
|
十一、常见问题解答
Q1: 什么时候选择组合式API,什么时候选择选项式API?
选择组合式API的场景:
- 需要更好的逻辑复用
- 组件逻辑复杂,需要更好的代码组织
- 使用TypeScript
- 需要更好的类型推断
选择选项式API的场景:
- 小型组件,逻辑简单
- 项目已经使用Vue 2迁移而来
- 团队更熟悉选项式API
- 不需要复杂的逻辑复用
Q2: 如何在组合式API中访问组件实例?
export default { setup() { const instance = getCurrentInstance() if (instance) { console.log(instance.appContext) } return {} } }
|
Q3: 如何在组合式API中访问路由和状态管理?
import { useRoute, useRouter } from 'vue-router' import { useStore } from 'vuex'
export default { setup() { const route = useRoute() const router = useRouter() const store = useStore() const goToHome = () => { router.push('/') } const increment = () => { store.commit('increment') } return { route, router, store, goToHome, increment } } }
|
Q4: 如何实现表单的双向绑定?
<template> <input v-model="formData.name" /> </template>
<script> import { ref } from 'vue'
export default { setup() { const formData = ref({ name: '', email: '' }) // 手动实现v-model const updateValue = (field, value) => { formData.value[field] = value } return { formData, updateValue } } } </script>
|
十二、总结
通过这篇文章,我们深入了解了Vue 3的组合式API。从基础语法到高级特性,从概念到实战,希望你能掌握这个强大的工具。
关键要点:
- 组合式API提供了更好的代码组织方式
- 自定义Hooks是逻辑复制的最佳实践
- 响应式系统更加灵活和强大
- 与TypeScript配合使用体验极佳
- 性能优化技巧在实际项目中很重要
学习建议:
- 从小组件开始,逐步过渡到复杂组件
- 多做实践,尝试将现有的选项式API组件转换为组合式API
- 阅读官方文档,Vue 3的官方文档是最好的学习资料
- 参与社区,与其他开发者交流经验
记住,技术没有绝对的优劣,选择最适合项目需求的方式才是最重要的。组合式API虽然强大,但也不是万能的。在实际项目中,可以根据团队的技术栈和项目特点,合理选择使用方式。
如果你在使用组合式API的过程中遇到任何问题,欢迎在评论区留言交流。祝大家学习愉快!🎉
最后更新:2026年5月14日
分类:Vue 3 | 前端开发 | JavaScript框架 | 组件化开发