返回文章列表
Vue3 组合式 API 与响应式原理深度解析
Vue3 的组合式 API(Composition API)不仅是新的代码组织方式,更是 Vue 对现代前端开发范式的回应。它解决了 Options API 在大型组件中代码组织混乱、逻辑复用困难的问题。本文深入讲解组合式 API 的核心概念和 Vue3 响应式系统的底层原理。
一、为什么需要组合式 API?
Options API 按类型组织代码(data、methods、computed、watch),在小型组件中清晰直观。但随着组件变大,同一功能的代码会被拆散到不同选项中,修改一个功能要在多个区域之间来回跳转。
// ❌ Options API:同一功能的代码散落各处
export default {
data() {
return { searchQuery: '', results: [], page: 1 }
},
computed: {
filteredResults() { /* 搜索过滤 */ },
hasMore() { /* 分页判断 */ }
},
methods: {
search() { /* 搜索请求 */ },
loadMore() { /* 加载更多 */ }
},
mounted() { this.search() }
}
// ✅ Composition API:功能内聚,逻辑集中
import { ref, computed, onMounted } from 'vue'
export default {
setup() {
// ===== 搜索功能(所有相关代码集中在一起)=====
const searchQuery = ref('')
const results = ref([])
const page = ref(1)
const filteredResults = computed(() =>
results.value.filter(item => item.title.includes(searchQuery.value))
)
async function search() {
const res = await fetch(`/api/search?q=${searchQuery.value}&page=${page.value}`)
results.value = await res.json()
}
onMounted(search)
return { searchQuery, filteredResults, search }
}
}
二、核心 API 详解
2.1 ref 与 reactive
import { ref, reactive, toRefs } from 'vue'
// ref:任意类型的响应式引用(通过 .value 访问)
const count = ref(0)
count.value++
const user = ref({ name: '张兴中', age: 25 })
// reactive:对象类型的响应式(直接访问属性)
const state = reactive({
name: '张兴中',
posts: [],
settings: { theme: 'dark' }
})
state.name = '新名字' // 无需 .value
// ⚠️ 解构 reactive 会丢失响应性,用 toRefs 解决
const { name, posts } = toRefs(state) // 保持响应式
// 解构后访问需要 .value
// 选型建议
// 基础类型 → ref;复杂嵌套对象 → reactive;两者均可时 → ref(更灵活,可整体替换)
2.2 computed 与 watch
import { ref, computed, watch, watchEffect } from 'vue'
const price = ref(100)
const quantity = ref(2)
// computed:带缓存的计算属性,依赖不变不重新计算
const total = computed(() => price.value * quantity.value)
// 可写的 computed
const fullName = computed({
get: () => `${firstName.value} ${lastName.value}`,
set: (val) => {
[firstName.value, lastName.value] = val.split(' ')
}
})
// watch:精确监听,可访问新旧值
watch(price, (newVal, oldVal) => {
})
// 监听多个源
watch([price, quantity], ([newP, newQ], [oldP, oldQ]) => {
})
// 深度监听对象
const user = ref({ address: { city: '盐城' } })
watch(user, (val) => , { deep: true, immediate: true })
// watchEffect:自动追踪所有依赖,立即执行
const stop = watchEffect(() => {
// 自动追踪 price 和 quantity
})
// 停止监听
stop()
2.3 生命周期钩子
import { onMounted, onUpdated, onUnmounted, onBeforeUnmount } from 'vue'
onMounted(() => {
// DOM 已挂载,可以操作 DOM、初始化第三方库
const chart = echarts.init(document.getElementById('chart'))
// ...
})
onBeforeUnmount(() => {
// 组件即将卸载,清理副作用
clearInterval(timer)
chart.dispose()
eventBus.off('message', handler)
})
// Options API → Composition API 对照
// beforeCreate / created → setup() 本身
// beforeMount → onBeforeMount
// mounted → onMounted
// beforeUpdate → onBeforeUpdate
// updated → onUpdated
// beforeUnmount → onBeforeUnmount
// unmounted → onUnmounted
三、自定义 Hook(Composable)
组合式 API 最大的威力在于逻辑复用——把相关逻辑封装为独立函数,跨组件共享:
// composables/useFetch.ts
import { ref, unref, watchEffect } from 'vue'
export function useFetch<T>(url: string | Ref<string>) {
const data = ref<T | null>(null)
const error = ref<string | null>(null)
const loading = ref(true)
async function doFetch() {
loading.value = true
error.value = null
try {
const res = await fetch(unref(url)) // unref 兼容普通值和 ref
if (!res.ok) throw new Error(`HTTP ${res.status}`)
data.value = await res.json()
} catch (err: any) {
error.value = err.message
} finally {
loading.value = false
}
}
// url 是响应式时自动重新请求
watchEffect(() => doFetch())
return { data, error, loading, refresh: doFetch }
}
// composables/useLocalStorage.ts
import { ref, watch } from 'vue'
export function useLocalStorage<T>(key: string, defaultValue: T) {
const stored = localStorage.getItem(key)
const data = ref<T>(stored ? JSON.parse(stored) : defaultValue)
watch(data, (val) => {
localStorage.setItem(key, JSON.stringify(val))
}, { deep: true })
return data
}
// 组件中使用
const { data: posts, loading, error } = useFetch('/api/posts')
const theme = useLocalStorage('theme', 'dark')
四、响应式原理:Proxy
Vue3 用 ES6 的 Proxy 重写响应式系统,取代了 Vue2 的 Object.defineProperty。
// 简化版响应式系统
let activeEffect = null
function track(target, key) {
if (!activeEffect) return
// 建立 target.key → effects 的映射,收集依赖
}
function trigger(target, key) {
// 找到 target.key 对应的所有 effects,重新执行
}
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
track(target, key) // 读取时收集依赖
const result = Reflect.get(target, key, receiver)
// 深层代理:嵌套对象也变为响应式
return typeof result === 'object' ? reactive(result) : result
},
set(target, key, value, receiver) {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
if (oldValue !== value) trigger(target, key) // 变化时触发更新
return result
},
deleteProperty(target, key) {
const hadKey = key in target
const result = Reflect.deleteProperty(target, key)
if (hadKey) trigger(target, key)
return result
}
})
}
// Proxy vs Object.defineProperty 优势
// ✅ 动态新增属性自动具备响应性(Vue2 需要 $set)
// ✅ 数组索引修改和 length 变化都能检测
// ✅ 支持 Map、Set、WeakMap 等数据结构
// ✅ 初始化性能更好(不需要遍历所有属性)
五、<script setup> 语法糖
Vue3.2 引入的 <script setup> 让组合式 API 更简洁:
<!-- ❌ 传统写法:需要手动 return -->
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return { count } // 必须 return 才能在模板使用
}
}
</script>
<!-- ✅ script setup:自动暴露,更简洁 -->
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useFetch } from './composables/useFetch'
const count = ref(0) // 自动在模板可用
const double = computed(() => count.value * 2)
const { data: posts, loading } = useFetch('/api/posts')
// defineProps / defineEmits 宏(无需 import)
const props = defineProps<{
title: string
initial?: number
}>()
const emit = defineEmits<{
change: [value: number]
submit: []
}>()
</script>
<template>
<div>
<h1>{{ props.title }}</h1>
<p v-if="loading">加载中...</p>
<p>{{ count }} × 2 = {{ double }}</p>
<button @click="count++">+1</button>
</div>
</template>
Vue3 的组合式 API 是现代 Vue 开发的正确姿势。理解 Proxy 响应式原理,掌握自定义 Hook 封装思路,用 <script setup> 简化代码——这三点就是 Vue3 进阶的核心。越用越会感受到它在大型项目中的优雅之处。