Vue3框架的Composition API配合reactive和ref实现了细粒度的响应式更新,其底层依赖ES6 Proxy代理对象完成依赖收集与派发更新的完整链路。前端开发中理解响应式原理,有助于编写高效的组件逻辑和排查渲染性能问题。本文从源码层面拆解Vue3响应式系统的核心机制。
Vue3响应式系统核心:Proxy代理与effect依赖收集
Vue2使用Object.defineProperty劫持对象属性,存在无法监听新增/删除属性、数组索引修改等问题。Vue3改用Proxy代理整个对象,拦截13种操作陷阱(trap),从根本上解决了限制。
响应式系统的核心流程分为三步:
1. reactive()将目标对象包装为Proxy,读写操作被拦截
2. effect()注册副作用函数,在执行过程中访问响应式数据时自动建立依赖关系
3. 数据修改时,Proxy的set陷阱触发,找到对应的effect函数并重新执行
// Vue3响应式核心实现(简化版)
const targetMap = new WeakMap() // 存储依赖关系
let activeEffect = null // 当前正在执行的effect
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) {
const handler = {
get(obj, key, receiver) {
const result = Reflect.get(obj, key, receiver)
track(target, key) // 读取时收集依赖
// 嵌套对象递归代理
if (typeof result === 'object' && result !== null) {
return reactive(result)
}
return result
},
set(obj, key, value, receiver) {
const oldValue = obj[key]
const result = Reflect.set(obj, key, value, receiver)
if (oldValue !== value) {
trigger(target, key) // 写入时触发更新
}
return result
},
deleteProperty(obj, key) {
const result = Reflect.deleteProperty(obj, key)
trigger(target, key)
return result
}
}
return new Proxy(target, handler)
}
ref与reactive的区别及RefImpl内部实现
reactive只能代理对象类型,number、string等原始值无法被Proxy拦截。ref通过包装对象(RefImpl)解决此问题,内部维护一个value属性,对.value的读写触发track和trigger。
// ref实现原理
function ref(value) {
if (isRef(value)) return value
const wrapper = {
__v_isRef: true,
_value: convert(value), // 对象类型调用reactive
_rawValue: value,
get value() {
track(wrapper, 'value')
return this._value
},
set value(newVal) {
if (newVal !== this._rawValue) {
this._rawValue = newVal
this._value = convert(newVal)
trigger(wrapper, 'value')
}
}
}
return wrapper
}
function convert(val) {
return typeof val === 'object' && val !== null
? reactive(val)
: val
}
// 判断是否为ref
function isRef(r) {
return r && r.__v_isRef === true
}
// 自动解包: template中无需.value
function unref(ref) {
return isRef(ref) ? ref.value : ref
}
// toRefs: 将reactive对象属性转为ref
function toRefs(object) {
const result = {}
for (const key in object) {
result[key] = toRef(object, key)
}
return result
}
function toRef(object, key) {
return {
__v_isRef: true,
get value() {
return object[key]
},
set value(newVal) {
object[key] = newVal
}
}
}
使用toRefs的场景是在Composition API中返回reactive对象时,解构后会丢失响应性。toRefs将每个属性转换为独立的ref,解构后仍保持响应式。
computed计算属性与watch侦听器的依赖追踪
computed本质是一个带缓存的effect,只有当依赖的响应式数据变化时才重新计算。其内部使用lazy标志延迟首次计算,dirty标志标记缓存是否过期。
// computed实现原理
function computed(getter) {
let value
let dirty = true
// 使用effect注册依赖, lazy模式不立即执行
const runner = effect(getter, {
lazy: true,
scheduler: () => {
// 依赖变化时不立即重新计算
// 标记为dirty, 下次访问时才计算
dirty = true
// 手动触发依赖computed的effect
trigger(computedRef, 'value')
}
})
const computedRef = {
__v_isRef: true,
get value() {
if (dirty) {
value = runner() // 执行getter, 收集依赖
dirty = false
}
track(computedRef, 'value')
return value
}
}
return computedRef
}
响应式性能优化:shallowReactive与markRaw
深层嵌套对象的递归代理在大型数据结构下存在性能开销。shallowReactive仅代理第一层属性,深层属性保持原始引用:
import { shallowReactive, shallowRef, markRaw } from 'vue'
// shallowReactive: 仅代理第一层
const state = shallowReactive({
user: { name: '张三', age: 25 },
list: [1, 2, 3] // list本身被代理, 但list[0]不会
})
// 修改第一层属性触发更新
state.count = 1 // 触发更新
// 修改深层属性不触发更新
state.user.name = '李四' // 不触发更新
// 需要替换整个对象
state.user = { name: '李四', age: 25 } // 触发更新
// markRaw: 标记对象永不转为响应式
const staticData = markRaw({
largeDataset: Array(100000).fill(0).map((_, i) => i)
})
const state2 = reactive({
chartData: staticData // chartData不会被代理
})
// readonly: 创建只读响应式代理
import { readonly } from 'vue'
const original = reactive({ count: 0 })
const copy = readonly(original)
// copy.count = 1 // 警告: Set operation on key "count" failed
实际项目中,图表大数据、地图实例、三方类库对象(如Three.js Scene、Monaco Editor)不需要响应式追踪,使用markRaw标记可避免不必要的Proxy包装开销。对于跨端小程序开发和Flutter移动端的响应式数据传递,shallowReactive配合手动触发更新是更优的性能方案。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3compositionapi-xiang-ying-shi-yuan-li-yu-reactiveref-yi/