vue 响应式原理
更新: 2025/3/12 17:59:43 字数: 0 字
响应式的本质
响应式的本质是函数和数据的关联,被监控的函数会和数据(响应式数据并且在函数中使用)关联起来,当数据变化时,会通知函数执行。
源码剖析
在 vue
中响应式被写在了 reactivity
这个包中。
其中有两个关键的方法,reactive
和 effect
。reactive
用于定义响应式数据,effect
负责进行依赖的收集。
示例
state
是定义的响应式数据,effect
是收集依赖的函数。当数据发生变化时,会触发对应的里面有该数据的 effect
中的函数执行。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script src="../reactivity/dist/@vue/reactivity.global.js"></script>
<script>
const { effect, reactive } = vueReactivity
let state = reactive({
name: '张三',
age: 18,
count: 0,
arr: [1, 2, 3],
obj: {
a: 1,
b: 2,
c: 3
}
});
// 1.里面的方法默认执行
// 2.获取代理的数据 ref proxy 执行 get 方法 收集effect
effect(() => {
app.innerHTML = state.name + state.arr.length
});
effect(() => {
app.innerHTML = state.name
});
// 执行了两边 effect 中的函数
setTimeout(() => {
// 修改数据会触发 set 执行 effect
state.name = '李四'
// state.arr.length = 1
}, 1000)
</script>
</body>
</html>
reactive 函数原理
ts
// 1. 根据函数来创建 reactive 对象,如果该对象已经是 响应式 那么就直接返回
export function reactive(target: object) {
// 如果试图观察只读代理,则返回只读版本。
// if trying to observe a readonly proxy, return the readonly version.
if (isReadonly(target)) {
return target
}
// 创建代理对象
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap,
)
}
// 2. 创建 raective 对象
/**
* target 是要代理的对象
* isReadonly 该对象是不是只能进行读取
* baseHandlers 是基本的代理处理器
* collectionHandlers 是你要代理的这一类的处理器
* proxyMap 是这一类的 map 集合,最后会将创建好的代理放进去
*/
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>,
) {
if (!isObject(target)) {
if (__DEV__) {
warn(
`value cannot be made ${isReadonly ? 'readonly' : 'reactive'}: ${String(
target,
)}`,
)
}
return target
}
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
if (target[ReactiveFlags.RAW] && !(isReadonly && target[ReactiveFlags.IS_REACTIVE])) {
return target
}
// target already has corresponding Proxy
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// only specific value types can be observed.
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// targetType 是用于判断数据类型的
// function getTargetType(value: Target) {
// return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
// ? TargetType.INVALID
// : targetTypeMap(toRawType(value))
// }
// export const toRawType = (value: unknown): string => {
// // extract "RawType" from strings like "[object RawType]"
// return toTypeString(value).slice(8, -1)
// }
// 数据 value 中有 __v_skip 字段
// 这个数据不能进行扩展 返回 TargetType.INVALID = 0
// 这个数据是可以进行扩展的 去寻找他的 toRawType 对应的值
// WARING:
// 上面经过一系列的判断来排除 target 已经被代理或者是其他类型
// 下面是创建代理对象
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
)
proxyMap.set(target, proxy)
return proxy
}
handler 处理器
proxy
要使用的 handler
的封装。
这里就是通过 proxy
对象来对数据进行代理,当数据被读取的时候会触发 get
方法,然后会收集依赖。
ts
// 1. 四种类型的 handler 处理器
export const mutableHandlers: ProxyHandler<object> =
/*@__PURE__*/ new MutableReactiveHandler()
export const readonlyHandlers: ProxyHandler<object> =
/*@__PURE__*/ new ReadonlyReactiveHandler()
export const shallowReactiveHandlers: MutableReactiveHandler =
/*@__PURE__*/ new MutableReactiveHandler(true)
// Props handlers are special in the sense that it should not unwrap top-level
// refs (in order to allow refs to be explicitly passed down), but should
// retain the reactivity of the normal readonly object.
export const shallowReadonlyHandlers: ReadonlyReactiveHandler =
/*@__PURE__*/ new ReadonlyReactiveHandler(true)
// 这四个 handler 处理器的类都继承了 BaseReactiveHandler
// 是对 ProxyHandler<Target> 接口的实现
// Target 是范型
class BaseReactiveHandler implements ProxyHandler<Target> {
constructor(
protected readonly _isReadonly = false,
protected readonly _isShallow = false,
) {}
// 这里对 get 方法做了统一的处理,也就是依赖的收集
// 这个 get 方法是对我们在 使用 例如:data.age 时对获取 age 方法的拦截
// target 是原始对象
// key 是要读取的键值
// receiver 是 proxy 本身
get(target: Target, key: string | symbol, receiver: object): any {
// 对于一些特有属性的 get 值的特殊处理
if (key === ReactiveFlags.SKIP) return target[ReactiveFlags.SKIP]
const isReadonly = this._isReadonly
const isShallow = this._isShallow
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
} else if (key === ReactiveFlags.IS_SHALLOW) {
return isShallow
} else if (key === ReactiveFlags.RAW) {
if (
receiver ===
(isReadonly
? isShallow
? shallowReadonlyMap
: readonlyMap
: isShallow
? shallowReactiveMap
: reactiveMap
).get(target) ||
// receiver is not the reactive proxy, but has the same prototype
// this means the receiver is a user proxy of the reactive proxy
Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)
) {
return target
}
// early return undefined
return
}
// 判断原始对象是不是数组
const targetIsArray = isArray(target)
if (!isReadonly) {
let fn: Function | undefined
if (targetIsArray && (fn = arrayInstrumentations[key])) {
return fn
}
if (key === 'hasOwnProperty') {
return hasOwnProperty
}
}
const res = Reflect.get(
target,
key,
// if this is a proxy wrapping a ref, return methods using the raw ref
// as receiver so that we don't have to call `toRaw` on the ref in all
// its class methods
isRef(target) ? target : receiver,
)
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res
}
if (!isReadonly) {
// 若是该对象不是只读的我们需要对其进行依赖的收集也就是 track
// 传入原始对象,GET,键值
track(target, TrackOpTypes.GET, key)
}
if (isShallow) {
return res
}
if (isRef(res)) {
// ref unwrapping - skip unwrap for Array + integer key.
return targetIsArray && isIntegerKey(key) ? res : res.value
}
if (isObject(res)) {
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
effect 函数
ts
// 传入函数和配置返回一个 ReactiveEffectRunner 对象
export function effect<T = any>(
fn: () => T,
options?: ReactiveEffectOptions,
): ReactiveEffectRunner<T> {
// 传入的 又是一个 effect 那么, fn 函数就是里面那个 efffect 的 fn 函数
if ((fn as ReactiveEffectRunner).effect instanceof ReactiveEffect) {
fn = (fn as ReactiveEffectRunner).effect.fn
}
// 创建 ReactiveEffect 对象
const e = new ReactiveEffect(fn)
if (options) {
extend(e, options)
}
try {
e.run()
} catch (err) {
e.stop()
throw err
}
const runner = e.run.bind(e) as ReactiveEffectRunner
runner.effect = e
return runner
}
ReactiveEffect 对象
ts
export let activeEffectScope: EffectScope | undefined
export class ReactiveEffect<T = any>
implements Subscriber, ReactiveEffectOptions
{
/**
* @internal
*/
deps?: Link = undefined
/**
* @internal
*/
depsTail?: Link = undefined
/**
* @internal
*/
flags: EffectFlags = EffectFlags.ACTIVE | EffectFlags.TRACKING
/**
* @internal
*/
next?: Subscriber = undefined
/**
* @internal
*/
cleanup?: () => void = undefined
scheduler?: EffectScheduler = undefined
onStop?: () => void
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
constructor(public fn: () => T) {
if (activeEffectScope && activeEffectScope.active) {
activeEffectScope.effects.push(this)
}
}
pause(): void {
this.flags |= EffectFlags.PAUSED
}
resume(): void {
if (this.flags & EffectFlags.PAUSED) {
this.flags &= ~EffectFlags.PAUSED
if (pausedQueueEffects.has(this)) {
pausedQueueEffects.delete(this)
this.trigger()
}
}
}
/**
* @internal
*/
notify(): void {
if (
this.flags & EffectFlags.RUNNING &&
!(this.flags & EffectFlags.ALLOW_RECURSE)
) {
return
}
if (!(this.flags & EffectFlags.NOTIFIED)) {
batch(this)
}
}
run(): T {
// TODO cleanupEffect
if (!(this.flags & EffectFlags.ACTIVE)) {
// stopped during cleanup
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 {
if (__DEV__ && activeSub !== this) {
warn(
'Active effect was not restored correctly - ' +
'this is likely a Vue internal bug.',
)
}
cleanupDeps(this)
activeSub = prevEffect
shouldTrack = prevShouldTrack
this.flags &= ~EffectFlags.RUNNING
}
}
stop(): void {
if (this.flags & EffectFlags.ACTIVE) {
for (let link = this.deps; link; link = link.nextDep) {
removeSub(link)
}
this.deps = this.depsTail = undefined
cleanupEffect(this)
this.onStop && this.onStop()
this.flags &= ~EffectFlags.ACTIVE
}
}
trigger(): void {
if (this.flags & EffectFlags.PAUSED) {
pausedQueueEffects.add(this)
} else if (this.scheduler) {
this.scheduler()
} else {
this.runIfDirty()
}
}
/**
* @internal
*/
runIfDirty(): void {
if (isDirty(this)) {
this.run()
}
}
get dirty(): boolean {
return isDirty(this)
}
}
track 依赖收集
ts
export let shouldTrack = true
export let activeSub: Subscriber | undefined // 当前活动的 sub
/**
* Tracks access to a reactive property.
*
* This will check which effect is running at the moment and record it as dep
* which records all effects that depend on the reactive property.
*
* @param target - Object holding the reactive property.
* @param type - Defines the type of access to the reactive property.
* @param key - Identifier of the reactive property to track.
*/
export function track(target: object, type: TrackOpTypes, key: unknown): void {
if (shouldTrack && activeSub) {
// 1. 通过原始数据在 targetMap 中找到目标数据
let depsMap = targetMap.get(target)
// 判断是否存在,不存在就 new 一个新的
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
// 2. 通过 key 从 依赖 Map 中查询对应的 key 对应的依赖
let dep = depsMap.get(key)
// 不存在就重新加入
if (!dep) {
depsMap.set(key, (dep = new Dep()))
dep.map = depsMap
dep.key = key
}
if (__DEV__) {
dep.track({
target,
type,
key,
})
} else {
dep.track()
}
}
}
//
// -- [key, value(key, dep)]
// 最后通过 Dep 这个数据类型来做依赖的收集
Dep 数据结构
ts
/**
* Represents a link between a source (Dep) and a subscriber (Effect or Computed).
* Deps and subs have a many-to-many relationship - each link between a
* dep and a sub is represented by a Link instance.
*
* A Link is also a node in two doubly-linked lists - one for the associated
* sub to track all its deps, and one for the associated dep to track all its
* subs.
*
* @internal
*/
export class Link {
/**
* - Before each effect run, all previous dep links' version are reset to -1
* - During the run, a link's version is synced with the source dep on access
* - After the run, links with version -1 (that were never used) are cleaned
* up
*/
version: number
/**
* Pointers for doubly-linked lists
*/
nextDep?: Link
prevDep?: Link
nextSub?: Link
prevSub?: Link
prevActiveLink?: Link
constructor(
public sub: Subscriber,
public dep: Dep,
) {
this.version = dep.version
this.nextDep =
this.prevDep =
this.nextSub =
this.prevSub =
this.prevActiveLink =
undefined
}
}