1. 组合式API深度解析
1.1 为什么需要组合式API
在Vue 2的Options API时代,我们经常遇到逻辑关注点分散的问题。一个功能相关的代码会被拆分到data、methods、computed等不同选项中,当组件复杂时,需要不断上下滚动查看相关代码。我在维护一个大型电商后台项目时,就遇到过400多行的组件文件,查找修改逻辑极其困难。
组合式API通过逻辑组合解决了这个问题。它允许我们将同一个功能相关的代码组织在一起,就像把分散的乐高积木按说明书分类整理。具体优势体现在:
- 逻辑复用更简单(可以直接提取use函数)
- 类型推导更友好(对TypeScript支持更好)
- 代码组织更灵活(不必再受options结构限制)
1.2 setup()函数实战指南
setup是组合式API的入口函数,它在组件实例创建之前执行。这里有个容易踩坑的地方:setup中不能使用this,因为此时组件实例还未创建。我推荐这样组织setup代码:
javascript复制import { ref, computed } from 'vue'
export default {
setup(props) {
// 状态声明
const count = ref(0)
const double = computed(() => count.value * 2)
// 方法声明
function increment() {
count.value++
}
// 生命周期钩子
onMounted(() => {
console.log('组件挂载完成')
})
// 必须return才能在模板中使用
return {
count,
double,
increment
}
}
}
重要提示:从setup返回的refs在模板中会自动解包,所以模板里直接写count而不是count.value
1.3 响应式系统核心
Vue 3的响应式系统基于Proxy重构,与Vue 2的defineProperty实现有本质区别。以下是常用的响应式API对比:
| API | 作用 | 适用场景 | 注意事项 |
|---|---|---|---|
| ref | 创建响应式引用 | 基本类型值 | 需要通过.value访问 |
| reactive | 创建响应式对象 | 对象/数组 | 不能解构会丢失响应性 |
| computed | 创建计算属性 | 派生状态 | 避免副作用操作 |
| watch | 侦听数据变化 | 异步操作 | 注意清理副作用 |
我在项目中总结的最佳实践是:
- 简单数据用ref
- 复杂对象用reactive
- 需要缓存的计算用computed
- 异步操作用watch
2. 生命周期函数完全指南
2.1 新旧生命周期对比
Vue 3的生命周期相比Vue 2有了一些变化,这是我在迁移项目时整理的对照表:
| Vue 2选项 | Vue 3组合式API | 触发时机 |
|---|---|---|
| beforeCreate | 无(直接用setup) | 实例初始化前 |
| created | 无(直接用setup) | 实例创建完成 |
| beforeMount | onBeforeMount | 挂载开始前 |
| mounted | onMounted | 挂载完成后 |
| beforeUpdate | onBeforeUpdate | 数据变化, DOM更新前 |
| updated | onUpdated | 数据变化, DOM更新后 |
| beforeUnmount | onBeforeUnmount | 卸载前(Vue2是beforeDestroy) |
| unmounted | onUnmounted | 卸载后(Vue2是destroyed) |
2.2 常见使用场景示例
数据获取的最佳实践:
javascript复制import { onMounted, ref } from 'vue'
export default {
setup() {
const data = ref(null)
const error = ref(null)
onMounted(async () => {
try {
const response = await fetch('/api/data')
data.value = await response.json()
} catch (err) {
error.value = err
}
})
return { data, error }
}
}
清理副作用的方法:
javascript复制import { onUnmounted } from 'vue'
export default {
setup() {
const timer = setInterval(() => {
console.log('计时器运行中')
}, 1000)
onUnmounted(() => {
clearInterval(timer)
})
}
}
2.3 生命周期使用陷阱
- 异步问题:在onMounted中获取的数据,可能在模板首次渲染时还未返回。解决方案是设置初始状态:
javascript复制const loading = ref(true)
onMounted(async () => {
data.value = await fetchData()
loading.value = false
})
- 执行顺序:当父子组件都有生命周期钩子时,执行顺序是:
- 父beforeMount
- 子beforeMount
- 子mounted
- 父mounted
- 重复触发:在setup顶层直接写代码相当于created钩子,但会被多次执行。应该把初始化逻辑放在onMounted中。
3. 组合式API进阶模式
3.1 逻辑复用技巧
我们可以把可复用的逻辑提取为组合式函数。比如这个鼠标位置跟踪器:
javascript复制// useMouse.js
import { ref, onMounted, onUnmounted } from 'vue'
export function useMouse() {
const x = ref(0)
const y = ref(0)
function update(e) {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
return { x, y }
}
// 组件中使用
import { useMouse } from './useMouse'
export default {
setup() {
const { x, y } = useMouse()
return { x, y }
}
}
3.2 依赖注入模式
组合式API中可以使用provide/inject实现跨组件通信:
javascript复制// 祖先组件
import { provide, ref } from 'vue'
export default {
setup() {
const location = ref('North Pole')
provide('location', location)
return { location }
}
}
// 后代组件
import { inject } from 'vue'
export default {
setup() {
const location = inject('location', '默认值')
return { location }
}
}
3.3 与TS的类型集成
组合式API对TypeScript支持非常好,这是带类型提示的组合函数示例:
typescript复制import { ref, Ref } from 'vue'
interface User {
id: number
name: string
}
export function useUser(): {
user: Ref<User | null>
fetchUser: (id: number) => Promise<void>
} {
const user = ref<User | null>(null)
async function fetchUser(id: number) {
user.value = await getUserById(id)
}
return {
user,
fetchUser
}
}
4. 实战问题排查手册
4.1 常见错误解决方案
问题1:"Cannot read property 'value' of null"
- 原因:在setup外部使用了ref
- 解决:确保所有ref操作都在setup函数内部
问题2:响应性丢失
- 现象:解构reactive对象后属性不再响应
- 解决:使用toRefs保持响应性:
javascript复制const state = reactive({ count: 0 })
const { count } = toRefs(state)
问题3:生命周期钩子不触发
- 检查点:
- 是否正确导入(如import { onMounted } from 'vue')
- 是否在setup函数内部调用
- 组件是否真的被挂载/卸载
4.2 性能优化建议
- 计算属性缓存:对于复杂计算,使用computed而不是方法
- watch优化:
- 使用flush: 'post'推迟到DOM更新后执行
- 对数组深度监听使用
- 避免不必要的响应式:静态数据不需要用ref/reactive包装
4.3 调试技巧
- 使用renderTracked和renderTriggered调试组件更新:
javascript复制import { onRenderTracked, onRenderTriggered } from 'vue'
export default {
setup() {
onRenderTracked((e) => {
console.log('依赖被跟踪', e)
})
onRenderTriggered((e) => {
console.log('依赖触发更新', e)
})
}
}
- 在Chrome开发者工具中安装Vue Devtools 6.x+版本,可以查看组合式API的调试信息