深入浅出Vue的响应性(上篇)运行完了effect,接下来一段setTimeout代码,下篇会大家一起探究setter过程都做了什么事。

上图这一段代码,Vue究竟做了什么事呢?大概有以下三件:
- 创建proxy;
- 收集effect的依赖;
- 触发收集的依赖;
接下来是分析setter过程,下图是setter调用流程图,能帮助大家理性思路;

触发setter行为会影响数据变化,触发视图的更新;触发setter方法,进入createSetter方法:
js
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
// 此时的result是true
const result = Reflect.set(target, key, value, receiver)
if (target === toRaw(receiver)) {
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
}
set中传入的target是{name: '张三'}
,key是name
,value是'李四'
, receiver是{name: '张三'}
;
第15行触发了trigger方法,上文提到了响应性的核心其实是依赖收集和依赖触发;上篇文章里说过track方法是响应性的核心方法,它是用来触发依赖收集的;这篇文章里trigger类似于track方法,不同的是trigger是用来依赖触发的;
下面是trigger方法:
js
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
const depsMap = targetMap.get(target)
if (!depsMap) {
// never been tracked
return
}
let deps: (Dep | undefined)[] = []
if () {
// 省略
} else {
if (key !== void 0) {
// key是"name",depsMap.get(key)是set,里面有ReactiveEffect
deps.push(depsMap.get(key))
}
// 此时的type是"set"
switch (type) {
case TriggerOpTypes.ADD:
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
}
} else if (isIntegerKey(key)) {
// new index added to array -> length changes
deps.push(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE:
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
case TriggerOpTypes.SET:
if (isMap(target)) {
deps.push(depsMap.get(ITERATE_KEY))
}
break
}
}
if (deps.length === 1) {
if (deps[0]) {
if (__DEV__) {
// 核心的依赖触发方法
triggerEffects(deps[0], eventInfo)
} else {
triggerEffects(deps[0])
}
}
}
}
第9行的targetMap此时如下图,因为targetMap是有值的,所以depsMap是 Map(1) {'name' => Set(1)}
;

第21行,key是"name",depsMap.get(key)是set,里面有ReactiveEffect,所以deps存放了set; 第24行,这里的type是set
, switch语句就会走set的逻辑;
第45行,判断target是不是map,此时的target是{name: '李四'}
对象,所以就直接break;
triggerEffects方法:
js
export function triggerEffects(
dep: Dep | ReactiveEffect[],
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
const effects = isArray(dep) ? dep : [...dep]
for (const effect of effects) {
if (!effect.computed) {
// 用triggerEffect触发依赖;
triggerEffect(effect, debuggerEventExtraInfo)
}
}
}
第5行,此时的dep就是Set(1) {ReactiveEffect}
;
第6行,此时的effects是[ReactiveEffect]
,ReactiveEffect的数组,它的fn还是我们之前的document.querySelector('#app').innerText = obj.name
;

第9行,用triggerEffect触发依赖; 接着我们看一下triggerEffect方法:
js
function triggerEffect(
effect: ReactiveEffect,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
if (effect !== activeEffect || effect.allowRecurse) {
if (effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
}
}
第9行,执行了effect.run方法,effect是reactiveEffect的实例,本质上是触发fn函数,也就是我们传入的() => { document.querySelector('#app').innerText = obj.name }
;此时的obj.name已经从张三
变成了李四
,执行fn是把李四
渲染到页面;
到这里setter的过程就结束了;
整个setter流程主要是做了两件事:
- 修改obj的值;
- 触发targetMap下保存的fn函数;

理清getter和setter的思路之后,发现实现响应性并没有想象中的困难啊,有了理论的加持,就该动手实践啦!加油!!