[Vue 源码] Vue 3.2 - Reactive 原理
代码运行结果
代码示例
<script src="./dist/reactivity.global.js"></script>
<body>
<div id="app"></div>
<script>
const { effect, reactive, ref, watch } = VueReactivity
const state = reactive({ name: 'cyan', age: 18 })
effect(() => {
app.innerHTML = state.name + state.age
})
setTimeout(() => {
state.name = 'tom'
}, 1000)
</script>
</body>
挂载阶段
执行第一句代码:const state = reactive({ name: 'cyan', age: 18 })
第一:reactive 会通过调用 createReactiveObject 函数来创建响应式对象。
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
)
}
第二: createReactiveObject 函数中 通过 proxy 和 baseHandlers 创建响应式对象。并将响应式对象返回给 state。
const proxy = new Proxy(
target,
targetType = baseHandlers
)
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
// 如果已经是一个响应式对象了,直接返回该响应式对象
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// 如果多次通过一个相同的对象,创建响应式对象,直接返回该响应式对象
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 不支持简单值
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// 创建响应式对象
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}
执行第二句代码 effect(() => {app.innerHTML = state.name + state.age})
第三:调用 effect 函数
- 通过 new ReactiveEffect 来创建 ReactiveEffect 对象。
- 执行 ReactiveEffect 对象上的 Run 方法,之后返回 run 方法,暴露给用户进行调度(可以用于批量更新)。
export function effect<T = any>(
fn: () => T,
options?: ReactiveEffectOptions
): ReactiveEffectRunner {
const _effect = new ReactiveEffect(fn)
_effect.run()
const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
runner.effect = _effect
return runner
}
第四:ReactiveEffect 类的 constructor 被调用, 将 effect 的参数作为 ReactiveEffect 对象的 fn 属性。
constructor(
public fn: () => T,
public scheduler: EffectScheduler | null = null,
scope?: EffectScope
) {
recordEffectScope(this, scope)
}
第五:调用 run 方法,当前 activeEffect 指向 effect 函数创建的 ReactiveEffect 对象,ReactiveEffect 对象的 fn 属性被调用, 调用过程中去访问了响应式对象 state(state.name + state.age
),从而触发响应式对象/代理Proxy 对象的 getter 方法。
ReactiveEffect.fn = () => {
app.innerHTML = state.name + state.age;
})
第六:在 state 的 getter 方法中
- 先通过 Reflect.get 取值。
- 调用 track 函数跟踪当前的 activeEffefcts 依赖对象。
- 如果是一个引用类型值,继续去通过 reactive 递归创建响应式对象。
- 返回 Reflect.get 的取值结果。
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
const res = Reflect.get(target, key, receiver)
track(target, TrackOpTypes.GET, key)
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
}
}
第七:通过 track函数, trackEffects 函数建立双 Map 结构跟踪属性依赖。结果是这样的:我们访问了 state.name, state.age
, 所以 name,age 对应的 映射列表里有 当前 activeEffects 也就是 ReactiveEffect 对象。
const targetMap = {
{name: "cyan", age: 18}: {
name: [ReactiveEffect],
age: [ReactiveEffect]
}
}
图中还有一个细节没有表示,每一个 ReactiveEffefct 对象的 deps 属性,都指向该属性的依赖对象列表。用于每次 run 之后清理依赖对象。
第七:到这里为止,初始化完毕。app.innerHTML 中初始值渲染完成。
更新阶段:
第一:当执行 state.name = "tom" 时,会触发响应式对象的 settter 操作,在 settter 操作中
- 通过 Reflect.set 改变响应式对象的值。
- 然后通过 trigger 函数,触发 ReactiveEffects 依赖对象。
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
// 如果新旧值相同,不更新
let oldValue = (target as any)[key]
if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
return false
}
const result = Reflect.set(target, key, value, receiver)
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
return result
}
第二:通过 trigger, triggerEffects, triggerEffect 三个函数,来触发更新,通过 cleanEffects 清理上次的依赖对象, 执行每个 ReactEffect 依赖对象的 run 方法,重新调用 ReactEffcts 对象的 fn属性。也就是 effect 函数的第一个参数。 也就是 javascriptReactiveEffect.fn = () => {app.innerHTML = state.name + state.age; })
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
const depsMap = targetMap.get(target)
triggerEffects(createDep(effects), eventInfo)
}
export function triggerEffects(
dep: Dep | ReactiveEffect[],
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
for (const effect of effects) {
if (!effect.computed) {
triggerEffect(effect, debuggerEventExtraInfo)
}
}
}
function triggerEffect(
effect: ReactiveEffect,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
effect.run()
}
第三:state.name 又去调用了响应式对象的 getter 方法,由于通过 Reflect.set 修改了值,所以再次访问就是最新值,同时 再次收集依赖,返回新值,等待下一次更新。
第四:至此更新完毕。
转载自:https://juejin.cn/post/7208708762265550885