vue3知识点总结
(个人总结使用,只能保证我自己看得懂,看不懂的可以留言交流)
reactive 响应式系统
vue3
的响应式系统是基于订阅发布者模式,通过 proxy
实现对数据的响应式化,
而依赖副作用的收集就是根据响应式化的数据来进行的。
因为 Proxy
本质上是对某个对象的劫持,这样它不仅仅可以监听对象某个属性值的变化,还可以监听对象属性的新增和删除;
而 Object.defineProperty
是给对象的某个已存在的属性添加对应的 getter
和 setter
,所以它只能监听这个属性值的变化,而不能去监听对象属性的新增和删除。
注意
这里需要明确的一点,`reactive` 只是做了数据的响应式化处理,使用 `proxy` 代理数据对象,并在 `get`、`set` 中完成了数据访问劫持和数据设置触发依赖。 `get` 和 `set` 是在有访问或设置操作进行是才会触发的, 而 `vue` 的依赖收集(track)实际上是要结合 `effect Api` 进行的。 例如:let foo = reactive('foo')
let consoleFoo = ()=>{
console.log(foo)
}
effect(consoleFoo)
在上述代码中,首先通过 reactive
对 foo
做了代理,而 consoleFoo
方法则访问了 foo
并打印,
然后在 effect
中传入的函数 consoleFoo
,在这个过程中,effect
会运行一遍传入的函数 consoleFoo
,
此时 consoleFoo
会被当做当前激活依赖存放在全局变量 activeEffect
上,而 consoleFoo
运行时访问了 foo
,此时会触发 get
,从而将当前的
activeEffect
(也就是 consoleFoo
),当做 foo
的依赖进行收集。
reactive 的基本实现
reactive API
的实现其实就是创建并返回了一个 proxy
对象,
将原始数据对象 raw
和 mutableHandlers
传递给 proxy
构造函数,
完成对象的响应式代理。
其中 mutableHandlers
位于 baseHandlers.ts
中,其具体实现中
Getter
实现数据访问劫持:
Getter
接受两个参数 isReadonly = false
,isShallow = false
,用于标记是否创建的是 readonly
或 shallow
并且 返回了一个 get
方法,在 get
方法内部
- 首先判断访问的
key
是否是__v_reactive
或__v_readonly
,并返回(isReadonly
和isReactive
实现), - 然后根据
Getter
接受两个参数判断isReadonly = false
,则调用track
做依赖收集。 - 判断
isShallow = true
,直接返回访问目标值res
- 如果访问目标值
res
是对象,则根据isReadonly
判断递归调用readonly Api
或reactive Api
并传入res
setter
实现数据设置派发更新:
将访问的目标对象 target
、设置的 key
,设置的值传递 trigger
方法 派发啊更新
effect的基本实现(runner、scheduler、stop)以及依赖收集与触发依赖
effect 主流程
effect
接受参数 effect
接受依赖函数 fn
和配置对象(内含调度执行函数 scheduler
和 onStop方法
),
- 方法内部会通过
ReactiveEffect
创建_effect
对象(_effect
接受依赖函数fn,和调度执行函数scheduler
), - 然后会将
onStop
通过Object.assign
拷贝到_effect
- 执行
_effect.run
(其实这里就是执行了 依赖函数fn
,因为创建_effect
时已经传递进去了),此时如果fn
内部访问了 响应式对象,fn则会当做依赖被收集到对应 响应式对象依赖中。 - 创建
runner
_effect.run.bind(_effect)
),并把_effect
存在runner
上(实现stop
方法) - 返回
runner
(_effect.run.bind(_effect)
),用户可以手动通过runner
触发依赖,
ReactiveEffect 对象
_fn // 依赖函数
deps = [] // 依赖函数集合
active = true // 是否激活,stop方法执行后用于判断
onStop = ()=>{
} // stop 钩子方法
scheduler
首先依赖收集有全局变量 activeEffect
(当前激活的 effec
t对象,内含副作用依赖),shouldTrack
(是否需要收集)
ReactiveEffect
对象还包含一个 run
方法 和 stop
方法,
在构造方法时,_fn
会缓存当前传入的依赖函数 fn
,
当外部调用 run
方法时,run
方法内部会先判断 active === false
,
命中则直接返回 依赖函数执行结果 this._fn()
;
否则 shouldTrack
设置为 true
就把 activeEffect
指向当前 ReactiveEffect
对象(用于 track
时收集),
执行结果 res = this._fn();
shouldTrack
设置为 false
最后返回 res
注意,实际上 activeEffect
指向当前 ReactiveEffect
对象这一步是通过effectStack 栈 维护的
const counter = reactive({
num: 0,
num2: 0
})
function logCount() {
effect(logCount2)
console.log('num:', counter.num)
}
function count() {
counter.num++
}
function logCount2() {
console.log('num2:', counter.num2)
}
effect(logCount)
count()
上述代码中,如果单纯的把activeEffect
指向当前 ReactiveEffect
对象,effect(logCount2)
执行完后 activeEffect 指向的是 logCount2
,而后续的 console.log('num:', counter.num)
,
会导致错误的将 logCount2
作为 num
的依赖收集,
此时我们count()
,触发的依赖却是 logCount2
,在 fn
执行完毕后出栈,再把 activeEffect
指向 effectStack
最后一个元素,
也就是外层 effect
函数对应的 reactiveEffect
当外部调用 stop
方法时,stop
方法内部会先判断 active === true
,
然后判断是否传入了 onStop
钩子函数,有就运行 onStop
。然后清除依赖,并把 active = false
,
注意
`stop` 方法想要实现的效果其实是 `stop` 后,清空收集的依赖并不在收集、不执行副作用函数,需要手动调用 `runner`, 这个效果实际上是通过 `shouldTrack` 和 `this.active` 实现的 正常情况下 `run` 方法运行,`shouldTrack` 设置为 `true` ,然后执行 `this._fn()`,在这个 `this._fn()` 过程中会触发 `track` 做依赖收集, 而 `track` 会判断 `shouldTrack`,为 `false` 直接返回不收集, 正常流程 `shouldTrack = true =》this._fn() =》 track 收集 =》shouldTrack = false`, 注意此流程走完后依赖收集完毕是 `false` 的, 而在调用 `stop` 方法后因为 `this.active` 变为 `false`,`run` 方法运行他会直接返回 `this._fn()` 的结果,不会在设置 `shouldTrack = true`,于是在 `track` 时会被直接返回捕收剂。 #### 为什么effect.run 每次运行都要清空effect对象上的依赖? 见 `why should cleanupEffect in ReactiveEffect` ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— #### stop方法Api `stop`方法`Api`,传入一个 `runner`,当 `trigger` 后 ,不执行副作用函数,需要手动调用 `runner`, 其内部就是通过传入的 `runner` 访问都 `effect` 对象,并调用 `effect` 对象上的 `stop` 方法track
先判断 shouldTrack
,为 false
直接返回不收集
全局维护了一个 targetMap
,key
是响应式原始对象 target
,值是 depsMap
。
depsMap
的 key
是 target
中的各个 key
,值是一个 deps
(本质是 Set
),
deps
中每个元素是都对应这这个 key
的一个 activeEffect
( ReactiveEffect
对象,依赖函数 fn
实际上就存储在 ReactiveEffect
的 _fn
上)
track
方法主要是对上述数据结构的一些维护,比如没有就创建,有就获取,最终创建或找到对应 target
的对应 key
的依赖集合 deps
,然后调用 trackEffects
把 activeEffect
收集到 deps
中,
值得注意的时 trackEffects
还反向收集 activeEffect.deps.push(dep)
,在清除依赖时使用
trigger
从 depsMap
中找到对应 target
对应 key
的 deps
,并