Vue.js 源码揭秘(二):响应式系统
本文深入 reactivity 包源码,解析 reactive、ref、effect 的实现原理。
一、响应式概览
┌─────────────────────────────────────────────────────────────┐
│ 响应式系统 │
├─────────────────────────────────────────────────────────────┤
│ │
│ reactive(obj) ──► Proxy ──► get: track / set: trigger │
│ │
│ ref(value) ──► RefImpl ──► get: track / set: trigger │
│ │
│ effect(fn) ──► ReactiveEffect ──► 自动收集依赖 │
│ │
│ computed(fn) ──► ComputedRefImpl ──► 惰性求值 + 缓存 │
│ │
└─────────────────────────────────────────────────────────────┘
二、reactive 实现
2.1 createReactiveObject
// packages/reactivity/src/reactive.ts
export function reactive(target) {
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
}
function createReactiveObject(
target,
isReadonly,
baseHandlers,
collectionHandlers,
proxyMap
) {
// 非对象直接返回
if (!isObject(target)) {
return target
}
// 已经是 Proxy,直接返回
if (target[ReactiveFlags.RAW]) {
return target
}
// 已有缓存,返回缓存
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 获取目标类型
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// 创建 Proxy
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION
? collectionHandlers // Map/Set
: baseHandlers // Object/Array
)
// 缓存
proxyMap.set(target, proxy)
return proxy
}
2.2 baseHandlers
// packages/reactivity/src/baseHandlers.ts
export const mutableHandlers: ProxyHandler<object> = {
get(target, key, receiver) {
// 特殊 key 处理
if (key === ReactiveFlags.IS_REACTIVE) return true
if (key === ReactiveFlags.RAW) return target
const res = Reflect.get(target, key, receiver)
// 依赖收集
track(target, TrackOpTypes.GET, key)
// 深层响应式
if (isObject(res)) {
return reactive(res)
}
return res
},
set(target, key, value, receiver) {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
// 触发更新
if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
return result
},
deleteProperty(target, key) {
const hadKey = hasOwn(target, key)
const result = Reflect.deleteProperty(target, key)
if (result && hadKey) {
trigger(target, TriggerOpTypes.DELETE, key)
}
return result
}
}
三、Dep 依赖容器
// packages/reactivity/src/dep.ts
export class Dep {
version = 0
activeLink?: Link
subs?: Link // 订阅者链表尾
subsHead?: Link // 订阅者链表头
map?: KeyToDepMap
key?: unknown
sc: number = 0 // 订阅者计数
constructor(public computed?: ComputedRefImpl) {}
track() {
if (!activeSub || !shouldTrack) return
let link = this.activeLink
if (!link || link.sub !== activeSub) {
// 创建新的 Link
link = {
dep: this,
sub: activeSub,
version: this.version,
nextDep: undefined,
prevDep: activeSub.depsTail,
nextSub: undefined,
prevSub: this.subs,
prevActiveLink: this.activeLink
}
// 添加到订阅者的依赖链表
if (activeSub.depsTail) {
activeSub.depsTail.nextDep = link
} else {
activeSub.deps = link
}
activeSub.depsTail = link
// 添加到 Dep 的订阅者链表
if (this.subs) {
this.subs.nextSub = link
}
this.subs = link
this.sc++
}
link.version = this.version
this.activeLink = link
}
trigger() {
this.version++
// 通知所有订阅者
for (let link = this.subs; link; link = link.prevSub) {
link.sub.notify()
}
}
}
四、ReactiveEffect
// packages/reactivity/src/effect.ts
export let activeSub: Subscriber | undefined
export class ReactiveEffect {
deps?: Link
depsTail?: Link
flags: EffectFlags = EffectFlags.ACTIVE | EffectFlags.TRACKING
constructor(public fn: () => any) {
// 注册到 effectScope
if (activeEffectScope?.active) {
activeEffectScope.effects.push(this)
}
}
run() {
if (!(this.flags & EffectFlags.ACTIVE)) {
return this.fn()
}
this.flags |= EffectFlags.RUNNING
cleanupEffect(this)
prepareDeps(this)
const prevEffect = activeSub
const prevShouldTrack = shouldTrack
activeSub = this
shouldTrack = true
try {
return this.fn()
} finally {
cleanupDeps(this)
activeSub = prevEffect
shouldTrack = prevShouldTrack
this.flags &= ~EffectFlags.RUNNING
}
}
notify() {
if (this.flags & EffectFlags.RUNNING) return
if (!(this.flags & EffectFlags.NOTIFIED)) {
batch(this)
}
}
stop() {
if (this.flags & EffectFlags.ACTIVE) {
for (let link = this.deps; link; link = link.nextDep) {
removeSub(link)
}
this.deps = this.depsTail = undefined
cleanupEffect(this)
this.flags &= ~EffectFlags.ACTIVE
}
}
}
4.1 effect 函数
export function effect(fn, options?) {
const e = new ReactiveEffect(fn)
if (options) {
extend(e, options)
}
e.run()
const runner = e.run.bind(e)
runner.effect = e
return runner
}
五、ref 实现
// packages/reactivity/src/ref.ts
class RefImpl {
_value: any
_rawValue: any
dep: Dep = new Dep()
readonly [ReactiveFlags.IS_REF] = true
constructor(value, isShallow) {
this._rawValue = isShallow ? value : toRaw(value)
this._value = isShallow ? value : toReactive(value)
}
get value() {
this.dep.track()
return this._value
}
set value(newValue) {
const oldValue = this._rawValue
const useDirectValue = this[ReactiveFlags.IS_SHALLOW] ||
isShallow(newValue) ||
isReadonly(newValue)
newValue = useDirectValue ? newValue : toRaw(newValue)
if (hasChanged(newValue, oldValue)) {
this._rawValue = newValue
this._value = useDirectValue ? newValue : toReactive(newValue)
this.dep.trigger()
}
}
}
export function ref(value) {
return createRef(value, false)
}
function createRef(rawValue, shallow) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
六、computed 实现
// packages/reactivity/src/computed.ts
class ComputedRefImpl {
_value: any
dep: Dep
effect: ReactiveEffect
readonly [ReactiveFlags.IS_REF] = true
readonly [ReactiveFlags.IS_READONLY]: boolean
constructor(getter, setter, isReadonly) {
this.dep = new Dep(this)
this[ReactiveFlags.IS_READONLY] = isReadonly
this.effect = new ReactiveEffect(() => getter(this._value))
this.effect.computed = this
this.effect.scheduler = () => {
// 依赖变化时,标记为脏
if (!(this.flags & EffectFlags.DIRTY)) {
this.flags |= EffectFlags.DIRTY
this.dep.trigger()
}
}
}
get value() {
// 脏检查,需要重新计算
if (this.flags & EffectFlags.DIRTY) {
this._value = this.effect.run()
this.flags &= ~EffectFlags.DIRTY
}
this.dep.track()
return this._value
}
set value(newValue) {
this._setter(newValue)
}
}
export function computed(getterOrOptions) {
let getter, setter
if (isFunction(getterOrOptions)) {
getter = getterOrOptions
setter = NOOP
} else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}
return new ComputedRefImpl(getter, setter, !setter)
}
七、批量更新
// packages/reactivity/src/effect.ts
let batchDepth = 0
let batchedSub: Subscriber | undefined
export function batch(sub: Subscriber) {
sub.flags |= EffectFlags.NOTIFIED
sub.next = batchedSub
batchedSub = sub
}
export function startBatch() {
batchDepth++
}
export function endBatch() {
if (--batchDepth > 0) return
let error
while (batchedSub) {
let e = batchedSub
batchedSub = undefined
while (e) {
const next = e.next
e.next = undefined
e.flags &= ~EffectFlags.NOTIFIED
if (e.flags & EffectFlags.ACTIVE) {
try {
e.trigger()
} catch (err) {
if (!error) error = err
}
}
e = next
}
}
if (error) throw error
}
八、依赖清理
function prepareDeps(sub: Subscriber) {
for (let link = sub.deps; link; link = link.nextDep) {
// 标记为待清理
link.version = -1
link.prevActiveLink = link.dep.activeLink
link.dep.activeLink = link
}
}
function cleanupDeps(sub: Subscriber) {
let head
let tail = sub.depsTail
let link = tail
while (link) {
const prev = link.prevDep
if (link.version === -1) {
// 未使用的依赖,移除
if (link === tail) tail = prev
removeSub(link)
removeDep(link)
} else {
head = link
}
link.dep.activeLink = link.prevActiveLink
link.prevActiveLink = undefined
link = prev
}
sub.deps = head
sub.depsTail = tail
}
九、响应式工具函数
// 判断是否为响应式
export function isReactive(value) {
return !!(value && value[ReactiveFlags.IS_REACTIVE])
}
// 判断是否为 ref
export function isRef(r) {
return r ? r[ReactiveFlags.IS_REF] === true : false
}
// 获取原始值
export function toRaw(observed) {
const raw = observed && observed[ReactiveFlags.RAW]
return raw ? toRaw(raw) : observed
}
// 标记为非响应式
export function markRaw(value) {
def(value, ReactiveFlags.SKIP, true)
return value
}
// 解包 ref
export function unref(ref) {
return isRef(ref) ? ref.value : ref
}
十、调试技巧
10.1 关键断点
// 依赖收集
packages/reactivity/src/dep.ts → Dep.track
// 触发更新
packages/reactivity/src/dep.ts → Dep.trigger
// effect 执行
packages/reactivity/src/effect.ts → ReactiveEffect.run
10.2 调试示例
import { reactive, effect } from 'vue'
const state = reactive({ count: 0 })
effect(() => {
console.log('count:', state.count)
// 断点:查看 activeSub、deps
})
state.count++ // 断点:查看 trigger 流程
十一、小结
Vue3 响应式系统的核心:
- Proxy:拦截对象操作,实现依赖收集和触发
- Dep:依赖容器,管理订阅者链表
- ReactiveEffect:副作用,自动追踪依赖
- 双向链表:高效的依赖管理和清理
- 批量更新:合并多次更新,提升性能
📦 源码地址:github.com/vuejs/core
下一篇:虚拟 DOM 与 Diff 算法
如果觉得有帮助,欢迎点赞收藏 👍