Vue3的Composition API彻底改变了组件逻辑的组织方式,响应式系统从Vue2的Object.defineProperty升级为Proxy代理,实现了更完整的响应式追踪能力。理解ref、reactive、computed的底层原理,并掌握Composable函数的封装模式,是构建可维护Vue3应用的基础。本文从响应式原理出发,结合实际场景讲解Composable函数设计。
Proxy响应式系统核心原理剖析
Vue3使用Proxy对象代理数据访问,通过track函数收集依赖,trigger函数触发更新。与Vue2的defineProperty相比,Proxy能拦截包括属性新增、删除、数组索引修改等在内的所有操作。
// 响应式系统简化实现
const targetMap = new WeakMap() // target -> depsMap
let activeEffect = null
function track(target, key) {
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let dep = depsMap.get(key)
if (!dep) {
dep = new Set()
depsMap.set(key, dep)
}
dep.add(activeEffect)
}
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const dep = depsMap.get(key)
if (dep) {
dep.forEach(effect => effect())
}
}
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 result = Reflect.deleteProperty(target, key)
trigger(target, key)
return result
}
})
}
activeEffect的赋值在effect函数执行时完成。Vue3的effectWatch是组件渲染函数的封装,当响应式数据变化时,effect重新执行,触发组件重渲染。
ref与reactive的使用场景与性能差异
ref通过RefImpl类包装原始值,访问时通过.value触发track/set触发trigger。reactive直接代理对象。两者的选择取决于数据类型和使用场景:
import { ref, reactive, watchEffect } from 'vue'
// ref适合原始值(string/number/boolean)和需要替换引用的场景
const count = ref(0)
const user = ref({ name: '张三', age: 25 })
// reactive适合对象和数组,不需要.value访问
const state = reactive({
items: [],
loading: false,
pagination: { page: 1, size: 20 }
})
// ref替换引用触发响应式更新
user.value = { name: '李四', age: 30 } // 整体替换
// reactive直接赋值新对象不会触发更新
// state = { items: [], loading: false } // 错误:丢失响应式
// reactive的解决方案
Object.assign(state, { items: [], loading: false })
// watchEffect自动收集依赖
watchEffect(() => {
console.log(`count: ${count.value}, items: ${state.items.length}`)
})
性能方面,ref的依赖收集更精确,只追踪.value访问;reactive递归代理嵌套对象,深度较大的对象会有初始化开销。对大型列表数据,使用shallowReactive或shallowRef避免深度代理:
import { shallowReactive, shallowRef, markRaw } from 'vue'
// shallowReactive只代理第一层,嵌套属性不响应式
const largeList = shallowReactive({
data: [], // data的push/splice不触发更新
total: 0
})
// 需要手动触发更新
largeList.data = newArrayData // 替换引用触发更新
// markRaw标记对象永不响应式
const mapInstance = markRaw(new maplibregl.Map(...))
// 第三方库实例不需要响应式追踪
computed计算属性缓存机制与依赖追踪
computed通过dirty标志实现惰性计算和缓存。只有依赖变化时标记为dirty,下次访问才重新计算。多次访问不触发重复计算:
import { ref, computed } from 'vue'
const price = ref(100)
const quantity = ref(3)
// computed缓存机制
const total = computed(() => {
console.log('计算执行') // 只在price或quantity变化时打印
return price.value * quantity.value
})
console.log(total.value) // 打印"计算执行",返回300
console.log(total.value) // 不打印,返回缓存的300
price.value = 200
console.log(total.value) // 打印"计算执行",返回600
console.log(total.value) // 不打印,返回缓存的600
computed支持可写模式,通过get和set函数实现双向计算属性:
const firstName = ref('张')
const lastName = ref('三')
const fullName = computed({
get() {
return `${firstName.value}${lastName.value}`
},
set(newValue) {
[firstName.value, lastName.value] = newValue.split(' ')
}
})
fullName.value = '李 四'
console.log(firstName.value) // '李'
console.log(lastName.value) // '四'
Composable函数封装模式与最佳实践
Composable函数以use前缀命名,返回响应式数据和操作方法。良好的Composable应具备单一职责、可组合性和自动清理能力。以下是几个实战Composable封装:
// useFetch - 数据请求Composable
import { ref, watchEffect, isRef, unref, onUnmounted } from 'vue'
export function useFetch(url, options = {}) {
const data = ref(null)
const error = ref(null)
const loading = ref(false)
let controller = null
const execute = async () => {
loading.value = true
error.value = null
controller?.abort()
controller = new AbortController()
try {
const response = await fetch(unref(url), {
...options,
signal: controller.signal
})
if (!response.ok) throw new Error(`HTTP ${response.status}`)
data.value = await response.json()
} catch (e) {
if (e.name !== 'AbortError') error.value = e
} finally {
loading.value = false
}
}
// 支持ref传入,自动监听变化
if (isRef(url)) {
watchEffect(execute)
} else {
execute()
}
// 组件卸载时取消请求
onUnmounted(() => controller?.abort())
return { data, error, loading, execute, abort: () => controller?.abort() }
}
// 使用
const { data: products, loading, error } = useFetch('/api/products')
const { data: searchResults } = useFetch(computed(() => `/api/search?q=${keyword.value}`))
// useEventListener - 事件监听自动清理Composable
import { onMounted, onUnmounted } from 'vue'
export function useEventListener(target, event, handler, options = {}) {
onMounted(() => {
target.addEventListener(event, handler, options)
})
onUnmounted(() => {
target.removeEventListener(event, handler, options)
})
}
// useLocalStorage - 持久化响应式数据
export function useLocalStorage(key, defaultValue) {
const stored = localStorage.getItem(key)
const data = ref(stored ? JSON.parse(stored) : defaultValue)
watch(data, (newVal) => {
localStorage.setItem(key, JSON.stringify(newVal))
}, { deep: true })
return data
}
// 使用
const theme = useLocalStorage('app-theme', 'light')
const sidebarCollapsed = useLocalStorage('sidebar-collapsed', false)
Composable组合与状态共享
多个Composable可以组合使用,实现复杂逻辑的模块化拆分。全局状态通过独立的响应式模块实现,避免Prop drilling:
// stores/userStore.js - 全局用户状态
import { reactive, readonly } from 'vue'
const state = reactive({
user: null,
token: null,
permissions: []
})
export function useUserStore() {
const login = async (credentials) => {
const response = await api.login(credentials)
state.user = response.user
state.token = response.token
state.permissions = response.permissions
}
const logout = () => {
state.user = null
state.token = null
state.permissions = []
}
const hasPermission = (perm) => state.permissions.includes(perm)
return {
state: readonly(state), // 只读状态防止外部直接修改
login,
logout,
hasPermission
}
}
// 组件中使用
const { state, login, hasPermission } = useUserStore()
// state是只读的,只能通过login/logout修改
这种模式无需引入Pinia即可实现轻量级状态管理。对于大型项目,Pinia提供了更完善的DevTools集成和TypeScript支持,但Composable模式在中小型项目中更加灵活轻量。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3compositionapi-xiang-ying-shi-xi-tong-yuan-li-yu-zi/