1. 组合式API深度解析
组合式API是Vue 3最核心的革新之一,它彻底改变了我们组织组件逻辑的方式。传统选项式API(Options API)将代码分散在data、methods、computed等不同选项中,而组合式API则允许我们按功能而非选项类型来组织代码。
1.1 组合式API的核心优势
在实际项目中,我发现组合式API主要解决了三个痛点:
- 逻辑复用困难:以前要通过mixins或高阶组件实现逻辑复用,现在一个简单的composable函数就能搞定。比如表单验证逻辑,可以这样封装:
javascript复制// useFormValidation.js
export function useFormValidation() {
const errors = ref({})
const validateEmail = (email) => {
if (!/.+@.+\..+/.test(email)) {
errors.value.email = 'Invalid email format'
}
}
return { errors, validateEmail }
}
- 代码组织混乱:一个复杂组件可能包含多个互相关联的功能点。以前这些代码分散在不同选项中,现在可以集中在一起:
javascript复制// 用户管理功能
const { users, fetchUsers } = useUserManagement()
// 权限校验功能
const { permissions, checkPermission } = usePermission()
- 类型推导友好:对TypeScript项目特别有用,组合式API能提供更精确的类型推断。
重要提示:从Vue 2迁移时,不必强制重写所有组件。新旧API可以共存,建议在新功能开发时优先采用组合式API。
1.2 setup()函数详解
setup函数是组合式API的入口,它有两点特别需要注意:
- 执行时机:在beforeCreate之前执行,此时组件实例尚未创建,所以无法访问this
- 参数解析:
- props:响应式对象,不要直接解构
- context:包含attrs、slots、emit等非响应式对象
推荐写法:
javascript复制export default {
setup(props, { emit }) {
// 正确的方式:使用toRefs保持响应式
const { title } = toRefs(props)
// 事件触发
const handleClick = () => {
emit('update', newValue)
}
return { title, handleClick }
}
}
1.3 响应式系统进阶
Vue 3的响应式系统基于Proxy重构,理解其原理能避免很多坑:
-
reactive vs ref:
- reactive:用于对象
- ref:用于基本类型,通过.value访问
- 在模板中ref会自动解包,无需.value
-
响应式转换陷阱:
javascript复制const state = reactive({ count: 0 })
// 错误!丢失响应性
let { count } = state
// 正确做法
import { toRef } from 'vue'
const count = toRef(state, 'count')
- 计算属性妙用:
javascript复制const doubleCount = computed({
get: () => state.count * 2,
set: (val) => { state.count = val / 2 }
})
2. 生命周期函数完全指南
2.1 新旧生命周期对比
Vue 3的生命周期有重要调整,这是我在实际项目中总结的对照表:
| Vue 2选项式API | Vue 3组合式API | 触发时机 |
|---|---|---|
| beforeCreate | 无(用setup代替) | - |
| created | 无(用setup代替) | - |
| beforeMount | onBeforeMount | DOM挂载前 |
| mounted | onMounted | DOM挂载后 |
| beforeUpdate | onBeforeUpdate | 数据变化,DOM更新前 |
| updated | onUpdated | 数据变化,DOM更新后 |
| beforeDestroy | onBeforeUnmount | 组件卸载前 |
| destroyed | onUnmounted | 组件卸载后 |
| errorCaptured | onErrorCaptured | 捕获子组件错误 |
2.2 生命周期实战技巧
- 异步请求的最佳位置:
javascript复制onMounted(async () => {
try {
data.value = await fetchData()
} catch (error) {
error.value = error
}
})
- 清理副作用:
javascript复制onMounted(() => {
const timer = setInterval(() => {
// 轮询逻辑
}, 1000)
onUnmounted(() => clearInterval(timer))
})
- 多个同类生命周期:
javascript复制// 可以注册多个同类型钩子,按注册顺序执行
onMounted(doSomething)
onMounted(doSomethingElse)
2.3 调试生命周期技巧
开发复杂组件时,我常用这个技巧快速定位问题:
javascript复制const logLifecycle = (name) => {
onMounted(() => console.log(`${name} mounted`))
onUnmounted(() => console.log(`${name} unmounted`))
}
// 在多个子组件中使用
logLifecycle('UserList')
3. 组合式API实战模式
3.1 自定义组合函数
这是我在电商项目中封装的购物车逻辑:
javascript复制// useCart.js
export function useCart() {
const cart = ref([])
const total = computed(() => cart.value.reduce((sum, item) => sum + item.price, 0))
const addToCart = (product) => {
const existing = cart.value.find(item => item.id === product.id)
existing ? existing.quantity++ : cart.value.push({ ...product, quantity: 1 })
}
return { cart, total, addToCart }
}
3.2 组件间逻辑共享
多个组件使用相同逻辑时的最佳实践:
javascript复制// parent.vue
import { provide } from 'vue'
import { useSharedLogic } from './composables/useSharedLogic'
setup() {
const sharedData = useSharedLogic()
provide('sharedKey', sharedData)
}
// child.vue
import { inject } from 'vue'
setup() {
const sharedData = inject('sharedKey')
return { sharedData }
}
3.3 与路由和状态管理集成
与Vue Router和Pinia配合使用的模式:
javascript复制import { useRoute } from 'vue-router'
import { useStore } from 'pinia'
setup() {
const route = useRoute()
const store = useStore()
watch(() => route.params.id, (newId) => {
store.fetchUser(newId)
})
}
4. 常见问题与性能优化
4.1 响应式丢失问题
这是新手最常遇到的坑:
javascript复制// 错误示例
const state = reactive({ foo: 1 })
const { foo } = state // 响应式丢失!
// 解决方案1:使用toRefs
const { foo } = toRefs(state)
// 解决方案2:直接通过state访问
state.foo
4.2 生命周期执行顺序
在父子组件中,生命周期的执行顺序是:
- 父组件onBeforeMount
- 子组件onBeforeMount
- 子组件onMounted
- 父组件onMounted
4.3 性能优化建议
- 计算属性缓存:
javascript复制// 好的做法 - 会被缓存
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
// 不好的做法 - 每次渲染都执行
const fullName = () => `${firstName.value} ${lastName.value}`
- 避免不必要的响应式:
javascript复制// 不需要响应式的数据
const staticData = markRaw({
constantValue: 42,
heavyObject: new HeavyClass()
})
- 批量更新:
javascript复制import { nextTick } from 'vue'
async function batchUpdate() {
state.a = 1
state.b = 2
await nextTick()
// DOM已经更新
}
5. 组合式API设计模式
5.1 状态管理模式
中小型项目可以不用Vuex/Pinia,直接用组合式API:
javascript复制// store.js
export const createStore = () => {
const state = reactive({
count: 0,
user: null
})
const increment = () => state.count++
return { state, increment }
}
// app.js
const store = createStore()
app.provide('store', store)
5.2 依赖注入模式
实现类似React Context的功能:
javascript复制// ThemeProvider.vue
const theme = reactive({ color: 'blue' })
provide('theme', theme)
// ThemedButton.vue
const theme = inject('theme')
5.3 高阶组件模式
虽然组合式API减少了HOC的使用,但某些场景仍然有用:
javascript复制function withLoading(component) {
return {
setup(props) {
const loading = ref(false)
return () => h(component, {
...props,
loading: loading.value
})
}
}
}
在大型项目中,我通常会建立一个composables目录,按功能领域组织各种组合函数。比如:
code复制src/
composables/
useForm.js
usePagination.js
useApi.js
useAuth.js
每个组合函数保持单一职责原则,通过良好的类型定义和文档注释,团队成员可以像搭积木一样快速构建复杂功能。这种开发体验是选项式API难以实现的。