04
PART
响应式系统(下)
REACTIVITY · 面向开发者的响应式 API
本章概览
上篇讲了响应式内核,本篇聚焦开发者天天在用的 API:reactive、ref、computed、watch,以及 toRef/toRefs/proxyRefs 的解构技巧。
理解这些 API 的内部实现,能帮你避开「为什么我改了值视图不更新」「为什么 reactive 解构后失效」这类典型坑。
4.1 reactive — 深层响应式代理
reactive 是 Vue 3 最核心的响应式 API,它通过 Proxy 拦截对象的所有操作:
// 第 1756 行
function reactive(target) {
if (isReadonly(target)) return target;
return createReactiveObject(
target, false,
mutableHandlers, mutableCollectionHandlers,
reactiveMap
);
}
createReactiveObject — 第 1798 行
整个流程分五步,缓存与类型分发是性能关键:
非对象直接返回(reactive(1) 无效,应使用 ref)
检查 __v_raw(已是代理)和 __v_skip(标记跳过)
从 proxyMap 缓存查找,避免重复代理同一个对象
判断类型:普通对象用 baseHandlers,Map/Set/WeakMap/WeakSet 用 collectionHandlers
创建 new Proxy(target, handlers) 并缓存
Proxy Handler 体系
Vue 3 使用继承链组织 Proxy Handler,最终落地为四个具体 handler:
mutableHandlers
BaseReactiveHandler → MutableReactiveHandler(shallow=false),普通对象可写代理
shallowReactiveHandlers
(shallow=true)只代理一层,不深层响应,性能更好
readonlyHandlers
ReadonlyReactiveHandler(shallow=false),只读代理,set 时告警
shallowReadonlyHandlers
(shallow=true)只读 + 浅层,适合 props 透传
get trap — 第 1365 行
读取属性时依次处理内部标记(__v_isReactive 等)、数组方法拦截(arrayInstrumentations)、收集依赖(track)、shallow 短路返回、Ref 自动解包、对象自动深层代理。这是「访问即追踪」的核心。
get(target, key, receiver) {
const isReadOnly2 = this._isReadonly;
const isShallow2 = this._isShallow;
// 1. 处理内部标记(__v_isReactive 等)
if (key === "__v_isReactive") return !isReadOnly2;
if (key === "__v_isReadonly") return isReadOnly2;
if (key === "__v_raw") return target;
// 2. 数组方法查找 arrayInstrumentations
const targetIsArray = isArray(target);
if (!isReadOnly2 && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver);
}
// 3. 获取值
const res = Reflect.get(target, key, receiver);
// 4. 收集依赖
if (!isReadOnly2) track(target, "get", key);
// 5. shallow 模式直接返回
if (isShallow2) return res;
// 6. Ref 自动解包
if (isRef(res)) return isArray(target) && isIntegerKey(key) ? res : res.value;
// 7. 对象自动深层代理
if (isObject(res)) return isReadOnly2 ? readonly(res) : reactive(res);
return res;
}
set trap(第 1423 行)负责派发更新,它需要区分“新增 key”和“修改已有 key”,分别触发 "add" 和 "set" 类型的通知。
4.2 ref — 基本值响应式
reactive 只能代理对象,而 ref 可以包装任意类型的值:
// 第 1881 行
class RefImpl {
constructor(value, shallow) {
this.dep = new Dep(); // 独立的 Dep 实例
this.__v_isRef = true;
this._rawValue = shallow ? value : toRaw(value);
this._value = shallow ? value : toReactive(value);
this.__v_isShallow = shallow;
}
get value() {
this.dep.track(); // 收集依赖
return this._value;
}
set value(newValue) {
const oldValue = this._rawValue;
if (hasChanged(newValue, oldValue)) {
this._rawValue = toRaw(newValue);
this._value = toReactive(newValue);
this.dep.trigger(); // 派发更新
}
}
}
reactive vs ref
reactive 适合对象/数组 ref 适合基本类型(string/number/boolean) reactive 访问属性无需 。value ref 需要 。value(模板中自动解包) reactive 不能替换整个对象 ref。value = newObj 可以替换
toRaw 与 toReactive
toRaw(value):剥除 Proxy 获取原始对象,用于存储 _rawValue toReactive(value):如果是对象则调用 reactive() 深层代理,否则直接返回 这保证了 ref({a: 1}) 的 。value 是响应式的
4.3 computed — 计算属性
computed 是 Vue 中最巧妙的 API。它既是消费者(依赖上游响应式数据),又是生产者(被下游 effect 依赖):
// 第 2047 行
class ComputedRefImpl {
constructor(getter, setter, isSSR) {
this.fn = getter;
this.setter = setter;
this.dep = new Dep(this); // 注意:传入 this
this.flags = 16; // DIRTY
this.globalVersion = globalVersion - 1;
}
get value() {
this.dep.track(); // 被下游依赖收集
refreshComputed(this); // 检查是否需要重算
if (this.deps) {
// 更新所有 link 的 version
let link = this.deps;
while (link) {
link.version = link.dep.version;
link = link.nextDep;
}
}
return this._value;
}
}
computed 的懒求值特性
computed 不会在依赖变化时立即重新计算,而是在被访问时才检查是否需要重算(通过 version 对比)。如果没有任何地方读取这个 computed,即使它依赖的数据变了也不会重新计算。这就是“懒求值”——只在需要时才做工作。
4.4 watch 与 watchEffect
watch 和 watchEffect 本质上都是创建一个 ReactiveEffect,但通过 scheduler 控制执行时机:
watch 的 source 支持多种形式:
// Ref
watch(countRef, (newVal, oldVal) => { ... })
// Getter 函数
watch(() => state.count, (newVal) => { ... })
// Reactive 对象(自动深层)
watch(reactiveState, (newState) => { ... })
// 多源数组
watch([ref1, ref2, () => state.prop], ([v1, v2, v3]) => { ... })
4.5 toRef / toRefs / proxyRefs
这三个工具函数用于响应式对象的“解构”场景:
const state = reactive({ name: 'Vue', version: 3 });
// toRef:为单个属性创建 ref
const nameRef = toRef(state, 'name');
// toRefs:为所有属性创建 ref 对象
const { name, version } = toRefs(state);
// proxyRefs:自动解包 ref 的代理
// (setupState 使用此机制,模板中无需 .value)
proxyRefs(第 1949 行)创建一个 Proxy,在 get 时自动解包 ref(isRef ? ref.value : value),在 set 时自动设置 ref 的 .value。这就是为什么在 setup() 中返回 { count }(ref),模板中可以直接用 {{ count }} 而不需要 {{ count.value }}。
我是安南,专注于前端框架源码拆解与分享
需要完整相关资料的可以打个:‘学习666‘,找我拿
如果你觉得今天这篇有收获,欢迎点赞、在看、转发三连,我们下篇见
夜雨聆风