本页目录
响应式系统(reactivity)
开始
官网
源码
重点
- reactive() / ref() / computed() 的实现
- 依赖收集 (track) 和 触发更新 (trigger)
- effect() 的运行机制(副作用函数调度)
- scheduler(任务调度队列)
- 浅层/只读/深层响应式的区别
三个核心角色
- 依赖容器 Dep:本质是 Set
,记录“谁在用这个数据”。 - 依赖图 targetMap:WeakMap<object, Map<key, Dep>>,按“对象 → key → 订阅它的副作用集合”组织。
- 副作用 ReactiveEffect:包装你的副作用函数(渲染/打印/计算等),记录它“订阅了哪些 dep”。
两条关键链路
- 读取链:读取 state.a → Proxy get → track(target,key) → 把当前活跃的 ReactiveEffect 放入该 key 的 Dep。
- 写入链:写入 state.a = 2 → Proxy set → trigger(target,key) → 取出 Dep,把里面的 ReactiveEffect 重新执行(或交给 scheduler)。
关键文件(用于断点)
- packages/reactivity/src/effect.ts:ReactiveEffect、track、trigger
- packages/reactivity/src/baseHandlers.ts:对象/数组的 get/set 拦截
- packages/reactivity/src/collectionHandlers.ts:Map/Set 等集合类型拦截
- packages/reactivity/src/ref.ts:ref/shallowRef
- packages/reactivity/src/computed.ts:computed(带缓存与失效标记 dirty)
reactive
js
const state = reactive({ count: 0 })
创建
- reactive(obj) → 调用 createReactiveObject
- 返回一个 Proxy,拦截 get/set/delete 操作(位于 baseHandlers.ts)
依赖收集(读取时)
- 读取 state.count → Proxy get
- 调用 track(target, key):
- 找到 targetMap.get(state),再找 depsMap.get(‘count’)
- 把当前活跃的 ReactiveEffect 加入 dep
触发更新(写入时)
- Proxy set → 调用 trigger(target, key)
- 从 targetMap 拿到对应的 dep
- 遍历 dep 中的所有 effect,依次执行或交给 scheduler
关键点
- reactive 基于 Proxy
- 按 “对象 → key → effect 集合” 组织依赖
- 依赖清理保证分支切换时不会泄漏
ref
js
const count = ref(0)
创建
- ref(value) → 生成 RefImpl 类实例
- 内部持有一个 _value 和一个 dep: Set
依赖收集
- 访问 count.value → get value
- 调用 trackRefValue(this) → 把活跃 effect 加入 dep
触发更新
- set value(newVal) → 先判断是否真的变化
- 调用 triggerRefValue(this) → 遍历 dep 执行所有 effect
关键点
- ref 针对原始值(不需要 Proxy)
- 只有 .value 属性做依赖收集和触发
- 对象 ref 内部会自动 reactive 化
computed
js
const double = computed(() => state.count * 2)
创建
- computed(getter) → 创建一个 ComputedRefImpl
- 内部持有一个 lazy effect(不会立即执行)
- 有 dirty 标记(初始为 true),和 value 缓存
依赖收集
第一次访问 double.value:
- 查 dirty → 为 true
- 执行内部 effect,计算新值,并收集它依赖的 reactive/ref
- dirty = false
- 把 ComputedRefImpl 自己作为 依赖源,track 到外部 effect
再次读取
- 下次 double.value → 检查 dirty → 重新执行 getter → 更新缓存
触发更新
- state.count 更新 → 触发 trigger → 通知内部 effect 的 scheduler
- scheduler 不直接执行 getter,而是:
- 标记 dirty = true
- 调用 triggerRefValue(this) → 通知依赖 double 的外部 effect(渲染等)
关键点
- computed = 带缓存的派生值
- 不会每次都计算,只有依赖变更后,下一次访问才重新计算
- 部分成两层依赖链:
- getter 依赖 → state
- 外部 effect 依赖 → computed
js
reactive(obj) ref(val) computed(getter)
↓ ↓ ↓
Proxy 包装 RefImpl 包装 ComputedRefImpl 包装
↓ ↓ ↓
get → track get value → track get value → dirty?
set → trigger set value → trigger true → run effect
false → 缓存
侦听器 watchers
watch
ts
watch(source, callback, options?)
- watch 内部会创建一个 ReactiveEffect,把 source 包装成 getter。
- 每次依赖变更时,effect 触发 → 对比新旧值 → 执行回调。
- onCleanup 提供“副作用清理”,适合防抖/请求取消。
watchEffect
ts
watchEffect(effectFn, options?)
- 不需要手动指定 source
- 直接把函数作为副作用执行 → 内部自动收集依赖
- 类似于 effect + watch 的结合体
调试重点
- watch
- packages/runtime-core/src/apiWatch.ts
- 关键函数:
- doWatch(source, cb, options)(watch / watchEffect 都会走这里)
- scheduler:触发时机控制
- job:回调包装函数(包含新旧值、cleanup)
- watchEffect
- 也是调用 doWatch,只是没有 cb(内部 effect 本身就是回调)
js
keyword.value = 'hi'
↓
Proxy.set → trigger()
↓
ReactiveEffect.scheduler → queueJob()
↓
flushJobs() → 执行 watch 的 job
↓
执行回调 cb(newVal, oldVal)