Vue3 响应式系统——ref 和 reactive
一、Vue3 响应式系统概述
Vue3 响应式包 @vue/reactivity,核心由三部分构成:
数据 (Proxy Object) —— 依赖收集 Track —— 触发更新 Trigger —— Effect 执行更新
核心目标:
- 拦截读取和设置操作
- 收集依赖
- 在数据变化时重新触发相关副作用
主要实现 API:
二、reactive() 执行机制
2.1 核心逻辑(核心源码)
function reactive(target) {
return createReactiveObject(target, false, mutableHandlers)
}
function createReactiveObject(target, isReadonly, baseHandlers) {
if (!isObject(target)) {
return target
}
if (target already has proxy) return existing proxy
const proxy = new Proxy(target, baseHandlers)
cache proxy
return proxy
}
Vue3 用 Proxy 拦截对象操作,比 Vue2 的
Object.defineProperty更强(能监听属性增删)。
2.2 reactive 的 handler(简化)
const mutableHandlers = {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
track(target, key)
return isObject(res) ? reactive(res) : res
},
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
}
}
三、依赖收集和触发更新:track() 和 trigger()
Vue 内部维护一个 全局的 activeEffect:
let activeEffect = null
function effect(fn) {
activeEffect = wrappedEffect(fn)
fn() // 执行一次用于收集依赖
activeEffect = null
}
每次读取(get)响应式数据时:
function track(target, key) {
if (!activeEffect) return
const depsMap = targetMap.get(target) || new Map()
const dep = depsMap.get(key) || new Set()
dep.add(activeEffect)
}
当数据被设置(set)时:
function trigger(target, key) {
const depsMap = targetMap.get(target)
const dep = depsMap?.get(key)
dep?.forEach(effect => effect())
}
-
track只在 读取时收集依赖 -
trigger只在 数据修改时触发 effect 重新执行
四、ref() 的设计与区别
4.1 ref 是什么?
ref() 主要用于包装 基本类型 (对于对象引用类型内部直接调用上面的 reactive()):
const count = ref(0)
其结构本质上是:
interface RefImpl {
value: T
}
源码核心:
function ref(rawValue) {
return createRef(rawValue)
}
function createRef(rawValue) {
const refImpl = {
_value: convert(rawValue),
dep: new Set(), // 区别于reactive引用类型复杂的多层嵌套数据结构封装dep,ref这里直接在实例中存放一个dep来实现
get value() {
trackRefValue(refImpl)
return refImpl._value
},
set value(newVal) {
if (hasChanged(newVal, refImpl._value)) {
refImpl._value = convert(newVal)
triggerRefValue(refImpl)
}
}
}
return refImpl
}
4.2 ref vs reactive 的本质区别
五、template / setup 中的自动 unwrap
在 Vue 模板中:
<p>{{ count }}</p>
如果 count 是一个 ref,它会 自动解包,模板中不需要写 .value。这是由编译阶段的 transform 实现的。
六、响应式系统执行流程图(简化)
reactive/ref 数据 -> Proxy getter -> track
│
effect 注册
│
数据 setter -> trigger
↓
重新执行 effect