深入探索Vue 3组合式API
深入探索Vue 3组合式API
- 深入探索Vue 3组合式API
- 一、组合式API诞生背景
- 1.1 Options API的局限性
- 1.2 设计目标
- 二、核心概念解析
- 2.1 setup() 函数:组合式API的基石
- 2.2 响应式系统:重新定义数据驱动
- 2.3 生命周期:全新的接入方式
- 2.4 响应式原理探秘
- 2.5 组合式API中的上下文处理
- 三、核心API深度解析
- 3.1 响应式工具
- 3.2 计算属性
- 3.3 副作用管理
- 四、逻辑复用模式
- 4.1 自定义组合函数
- 4.2 异步状态管理
- 五、最佳实践
- 5.1 代码组织模式
- 5.2 TypeScript集成
- 六、与Options API对比
- 6.1 逻辑组织对比
深入探索Vue 3组合式API
一、组合式API诞生背景
1.1 Options API的局限性
- 代码组织碎片化
- 逻辑复用困难(mixins缺陷)
- 类型支持不足
1.2 设计目标
- 更好的逻辑复用
- 更灵活的组织方式
- 更好的类型推导
- 渐进式采用策略
二、核心概念解析
2.1 setup() 函数:组合式API的基石
(1)函数特性与执行时机
export default {
setup(props, context) {
// 在beforeCreate之前执行
// 无法访问this
// 返回对象将暴露给模板和其他选项
}
}
- 执行顺序:在组件实例创建之前同步执行,位于
beforeCreate
和created
生命周期之间 - 参数解析:
props
:响应式的props对象,结构会失去响应性context
:包含attrs、slots、emit的非响应式对象
- 返回值:
- 返回对象属性将合并到模板渲染上下文
- 可返回渲染函数直接控制视图输出
(2)props处理规范
import { defineComponent } from 'vue'
interface Props {
title: string
count?: number
}
export default defineComponent({
props: {
title: String,
count: {
type: Number,
default: 0
}
},
setup(props: Props) {
// 使用watch监听props变化
watch(() => props.count, (newVal) => {
console.log('count changed:', newVal)
})
}
})
- 类型安全:配合TypeScript实现严格类型校验
- 不可解构:直接解构props会导致响应性丢失,需使用
toRefs
- 默认值处理:当父组件未传值时自动应用默认值
(3)上下文对象解析
setup(props, { attrs, slots, emit, expose }) {
// 访问非响应式属性:
console.log(attrs.class)
// 检查插槽内容:
const hasFooter = slots.footer
// 事件触发:
emit('submit', payload)
// 暴露公共属性:
expose({ publicMethod })
}
- attrs:包含未在props中声明的属性
- slots:访问通过插槽分发的内容(v-slot语法)
- emit:替代this.$emit的事件触发方式
- expose:控制组件实例对外暴露的公共方法
(4)响应式状态管理
const state = reactive({
user: {
name: 'Alice',
posts: []
},
loading: false
})
// 嵌套对象自动响应化
watchEffect(() => {
console.log('User name changed:', state.user.name)
})
- 深层响应性:reactive会递归转换对象属性
- 数组处理:支持数组索引修改和length变更检测
- 自动解包:在模板中访问ref无需.value,但JS环境中需要
2.2 响应式系统:重新定义数据驱动
(1)响应式核心原理
const targetMap = new WeakMap()
function track(target, key) {
// 收集依赖
}
function trigger(target, key) {
// 通知更新
}
const handler = {
get(target, key, receiver) {
track(target, key)
return Reflect.get(...arguments)
},
set(target, key, value, receiver) {
const result = Reflect.set(...arguments)
trigger(target, key)
return result
}
}
- Proxy代理:基于ES6 Proxy实现属性访问拦截
- 依赖追踪:通过WeakMap建立目标对象->属性->依赖的映射关系
- 批量更新:Vue的调度机制确保多次状态变更合并为单次更新
(2)ref的进阶用法
// DOM元素引用
const inputRef = ref<HTMLInputElement | null>(null)
// 组件挂载后访问
onMounted(() => {
inputRef.value?.focus()
})
// 模板引用
<template>
<input ref="inputRef">
</template>
// 复杂类型处理
const state = ref({
user: {
name: 'Bob'
}
})
// 自动解包
state.value.user.name = 'Charlie'
- 模板引用:替代this.$refs的声明方式
- 类型标注:在TypeScript中明确指定引用类型
- 对象嵌套:ref可以包裹复杂对象结构
(3)reactive的边界情况
// 响应式丢失场景
const state = reactive({ x: 0 })
let { x } = state // 值拷贝,失去响应性
// 正确解构方式
const { x } = toRefs(state)
// 数组处理特例
const list = reactive([1, 2, 3])
list = reactive([4,5,6]) // 错误!需要修改现有引用
list.push(4) // 正确方式
// 使用readonly保护
const protectedState = readonly(state)
protectedState.x++ // 控制台警告
- 引用替换限制:必须保持对象引用不变
- 数组变异方法:push/pop等标准方法可触发更新
- 只读保护:防止意外修改共享状态
(4)响应式工具函数对比
方法 | 作用 | 典型场景 |
---|---|---|
toRef | 为源响应式对象属性创建ref | props解构时保持响应性 |
toRefs | 转换整个响应式对象为普通对象 | 从组合函数返回响应式状态 |
isProxy | 检查是否为代理对象 | 调试响应式系统 |
isReactive | 检查reactive创建的代理 | 类型判断 |
isReadonly | 检查只读代理 | 安全校验 |
markRaw | 标记对象永不转为响应式 | 性能优化/集成第三方库 |
shallowRef | 创建浅层ref | 大型对象性能优化 |
triggerRef | 手动触发shallowRef更新 | 强制刷新界面 |
2.3 生命周期:全新的接入方式
(1)完整生命周期映射表
Options API | Composition API | 触发时机 |
---|---|---|
beforeCreate | - | 被setup替代 |
created | - | 被setup替代 |
beforeMount | onBeforeMount | DOM挂载开始前 |
mounted | onMounted | DOM挂载完成后 |
beforeUpdate | onBeforeUpdate | 响应式数据变更导致更新前 |
updated | onUpdated | 虚拟DOM重新渲染后 |
beforeUnmount | onBeforeUnmount | 组件卸载前(vue2的beforeDestroy别名) |
unmounted | onUnmounted | 组件卸载后(vue2的destroyed别名) |
errorCaptured | onErrorCaptured | 捕获后代组件错误时 |
renderTracked | onRenderTracked | 响应式依赖被追踪时(开发模式) |
renderTriggered | onRenderTriggered | 响应式依赖触发更新时(开发模式) |
(2)组合式生命周期示例
import {
onMounted,
onUpdated,
onUnmounted
} from 'vue'
export default {
setup() {
// 同步调用保证正确注册
onMounted(async () => {
const data = await fetchData()
// 异步操作不会阻塞生命周期
})
// 支持多次注册相同钩子
onMounted(() => console.log('第一个mounted回调'))
onMounted(() => console.log('第二个mounted回调'))
// 清理副作用示例
const timer = ref()
onMounted(() => {
timer.value = setInterval(() => {
/* 定时任务 */
}, 1000)
})
onUnmounted(() => {
clearInterval(timer.value)
})
}
}
(3)生命周期使用原则
- 同步注册:必须在setup同步调用生命周期钩子
// 错误示例 setTimeout(() => { onMounted(() => {}) // 将不会执行 }, 100)
- 执行顺序:按照注册顺序同步执行
- 异步操作:钩子函数本身可以包含异步代码,但不会延迟生命周期进度
- 组件树顺序:父组件onBeforeMount先于子组件onBeforeMount
(4)调试钩子实践
onRenderTracked((event) => {
console.log('依赖追踪:', event)
})
onRenderTriggered((event) => {
console.log('依赖触发更新:', event)
})
- 开发模式专用:帮助分析组件渲染行为
- 事件对象:包含target(响应式对象)、key(触发属性)、type(操作类型)等信息
- 性能优化:识别不必要的渲染触发源
2.4 响应式原理探秘
(1)依赖收集流程
// 伪代码实现
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
track(target, key)
return Reflect.get(target, key)
},
set(target, key, value) {
const result = Reflect.set(target, key, value)
trigger(target, key)
return result
}
})
}
// 副作用函数注册
let activeEffect
class ReactiveEffect {
constructor(fn) {
this.fn = fn
}
run() {
activeEffect = this
return this.fn()
}
}
function watchEffect(fn) {
const effect = new ReactiveEffect(fn)
effect.run()
}
(2)响应式类型对比
ref | reactive | shallowRef | |
---|---|---|---|
创建方式 | ref(value) | reactive(object) | shallowRef(value) |
访问方式 | .value | 直接访问属性 | .value |
嵌套响应 | 自动展开 | 递归响应 | 非递归 |
类型支持 | 基础类型/对象 | 仅对象 | 任意类型 |
模板自动解包 | 支持 | 不需要 | 支持 |
适用场景 | 独立基本值、模板引用 | 复杂对象结构 | 大型对象性能优化 |
(3)响应式转换规则
const raw = {}
const observed = reactive(raw)
console.log(observed === raw) // false
console.log(reactive(observed) === observed) // true
const refVal = ref(0)
console.log(ref(refVal) === refVal) // true
- 代理唯一性:对同一原始对象多次调用reactive返回相同代理
- ref保护机制:如果传入ref给ref构造函数,直接返回原ref
- 原始对象保护:Vue不会代理Vue实例或代理对象
2.5 组合式API中的上下文处理
(1)跨层级访问示例
// 祖先组件
import { provide } from 'vue'
setup() {
const theme = ref('dark')
provide('theme', theme)
}
// 后代组件
import { inject } from 'vue'
setup() {
const theme = inject('theme', 'light') // 默认值
return { theme }
}
(2)模板引用转发
// 子组件
import { defineExpose } from 'vue'
setup() {
const publicMethod = () => { /* ... */ }
defineExpose({ publicMethod })
}
// 父组件
const childRef = ref()
onMounted(() => {
childRef.value.publicMethod()
})
三、核心API深度解析
3.1 响应式工具
// 解构响应式对象
import { reactive, toRefs } from 'vue'
const state = reactive({
x: 0,
y: 0
})
const { x, y } = toRefs(state)
3.2 计算属性
const doubleCount = computed(() => count.value * 2)
3.3 副作用管理
// watchEffect自动追踪依赖
const stop = watchEffect(() => {
console.log(`count变化: ${count.value}`)
})
// 精确控制的watch
watch(count, (newVal, oldVal) => {
// 执行特定操作
})
四、逻辑复用模式
4.1 自定义组合函数
// 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 }
}
}
4.2 异步状态管理
// useFetch.js
import { ref } from 'vue'
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
const loading = ref(false)
async function fetchData() {
try {
loading.value = true
const response = await fetch(url)
data.value = await response.json()
} catch (err) {
error.value = err
} finally {
loading.value = false
}
}
return {
data,
error,
loading,
fetchData
}
}
五、最佳实践
5.1 代码组织模式
export default {
setup() {
// 数据逻辑
const { x, y } = useMouse()
// 业务逻辑
const { data, fetch } = useFetch('/api')
// 其他逻辑
const count = useCounter()
return { x, y, data, fetch, count }
}
}
5.2 TypeScript集成
interface User {
id: number
name: string
}
const users = ref<User[]>([])
六、与Options API对比
6.1 逻辑组织对比
// Options API
export default {
data() {
return { count: 0 }
},
methods: {
increment() { this.count++ }
},
mounted() {
console.log('挂载完成')
}
}
// Composition API
export default {
setup() {
const count = ref(0)
const increment = () => count.value++
onMounted(() => console.log('挂载完成'))
return { count, increment }
}
}