Vue3响应式系统Proxy拦截机制
Vue3的响应式系统基于ES6 Proxy实现,相比Vue2的Object.defineProperty方案,Proxy能直接拦截对象属性的增删操作,无需递归遍历,支持数组索引修改和Map/Set集合类型的响应式追踪。reactive函数用Proxy包装目标对象,在get拦截器中收集依赖,在set拦截器中触发更新,形成完整的依赖追踪与派发更新闭环。
核心实现逻辑的简化版本有助于理解运行机制:
const targetMap = new WeakMap()
let activeEffect = null
function effect(fn) {
activeEffect = fn
fn()
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(obj, key) {
track(obj, key)
return Reflect.get(obj, key)
},
set(obj, key, value) {
Reflect.set(obj, key, value)
trigger(obj, key)
return true
}
})
}
track函数在属性被读取时调用,将当前的activeEffect(即正在执行的副作用函数)记录到该属性的依赖集合中。trigger函数在属性被修改时调用,遍历依赖集合依次执行所有副作用函数,实现数据变化到视图更新的自动派发。WeakMap的使用确保目标对象被垃圾回收时,关联的依赖关系也能被自动清理。
ref与reactive的使用场景区分
reactive只能代理对象类型,对原始值(string、number、boolean)无法使用。ref通过将原始值包装为{value: x}对象来解决这个限制。ref的内部实现使用get/set访问器拦截value属性:
function ref(value) {
const wrapper = {
get value() {
track(wrapper, 'value')
return value
},
set value(newVal) {
value = newVal
trigger(wrapper, 'value')
}
}
return wrapper
}
// 使用示例
const count = ref(0)
const state = reactive({ name: 'vue', list: [] })
effect(() => {
console.log(`count: ${count.value}, name: ${state.name}`)
})
count.value++ // 输出: count: 1, name: vue
state.name = 'vue3' // 输出: count: 1, name: vue3
模板编译时,ref类型的变量会被自动解包,直接使用变量名而无需.value后缀。但在JavaScript逻辑中访问必须通过.value。一个实用原则:基本类型用ref,对象和数组用reactive。如果需要在reactive对象中嵌套使用ref,Vue3会自动解包第一层ref,深层嵌套仍需手动处理。
自定义Hook函数封装与复用
Composition API的核心优势在于逻辑可提取复用。将相关状态和操作封装为自定义Hook函数,是Vue3项目组织的推荐模式。以下是一个带有防抖和请求取消功能的异步数据加载Hook:
import { ref, onUnmounted, isRef } from 'vue'
export function useFetchData(url, options = {}) {
const {
immediate = true,
initialData = null,
debounce = 0,
transform = (data) => data
} = options
const data = ref(initialData)
const loading = ref(false)
const error = ref(null)
let abortController = null
let debounceTimer = null
async function execute(params = {}) {
if (abortController) {
abortController.abort()
}
abortController = new AbortController()
loading.value = true
error.value = null
try {
const queryString = new URLSearchParams(params).toString()
const fullUrl = queryString ? `${url}?${queryString}` : url
const response = await fetch(fullUrl, {
signal: abortController.signal,
headers: { 'Content-Type': 'application/json' }
})
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
const rawData = await response.json()
data.value = transform(rawData)
} catch (err) {
if (err.name !== 'AbortError') {
error.value = err.message
}
} finally {
loading.value = false
}
}
function debouncedExecute(params = {}) {
if (debounceTimer) clearTimeout(debounceTimer)
debounceTimer = setTimeout(() => execute(params), debounce)
}
function refresh() {
return execute()
}
onUnmounted(() => {
if (abortController) abortController.abort()
if (debounceTimer) clearTimeout(debounceTimer)
})
if (immediate) {
execute()
}
return {
data,
loading,
error,
execute: debounce > 0 ? debouncedExecute : execute,
refresh
}
}
该Hook在组件中使用时,所有异步加载逻辑只需一行声明:
import { useFetchData } from '@/hooks/useFetchData'
export default {
setup() {
const { data, loading, error, refresh } = useFetchData(
'/api/users',
{
immediate: true,
debounce: 300,
transform: (raw) => raw.list
}
)
return { data, loading, error, refresh }
}
}
computed与watch的依赖追踪实现
computed函数创建惰性响应式计算属性,只有在被访问时才计算,且依赖未变化时返回缓存值。其实现原理是在effect基础上增加缓存标识和脏值检测:
function computed(getter) {
let cachedValue
let dirty = true
const runner = effect(() => {
// 依赖变化时标记为脏值
dirty = true
cachedValue = getter()
})
// runner首次执行后,依赖已建立
// 但需要让runner在依赖变化时不立即执行
// 实际实现中通过scheduler选项控制
return {
get value() {
if (dirty) {
dirty = false
runner()
}
return cachedValue
}
}
}
watch函数监听响应式数据变化并执行副作用,支持监听单个ref、reactive对象的属性函数返回值、或多个数据源的数组。与直接使用effect不同,watch允许访问变化前后的值(oldValue和newValue),并通过flush选项控制回调执行时机:默认在组件更新前同步执行,设置flush:’post’推迟到DOM更新后执行,适合需要操作更新后DOM的场景。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3compositionapi-xiang-ying-shi-xi-tong-yuan-li-yu-zi/