广告:宝塔Linux面板高效运维的服务器管理软件 点击【 https://www.bt.cn/p/uNLv1L 】立即购买
通常情况下我们是不会直接使用effect的,因为effect是一个底层的API,在我们使用Vue3的时候Vue默认会帮我们调用effect。 effect翻译为作用,意思是使其发生作用,这个使其的其就是我们传入的函数,所以effect的作用就是让我们传入的函数发生作用,也就是执行这个函数。 执行过程简图如下:
接下来先通过例子了解effect的基本用法,然后再去了解原理。
一、effect用法1、基本用法const obj = reactive({count: 1})const runner = effect(() => { console.log(obj.count)})obj.count++登录后复制
结果会先打印1, 然后在obj.count++
之后打印出2。
流程简图如下:
运行effect(fun)
// 先执行fun() // 打印出1const runner = new ReactiveEffect(fn)return runnerrunner: { run() { this.fun() //执行fun }, stop() { }}登录后复制
console.log(obj.count)
track依赖收集 结构如下:
obj.count++
触发依赖,执行runner.run(), 实际运行的是
() => { console.log(obj.count)}登录后复制
所以又打印出2
2、lazy属性为true此值为 true 时,只有在第一次手动调用 runner 后,依赖数据变更时,才会自动执行 effect 的回调,可以理解为 effect 的是在手动调用 runner 后才首次执行
const obj = reactive({count: 1})const runner = effect(() => { console.log(obj.count)}, { lazy: true})runner()obj.count++登录后复制
只会打印出2
原因是effect源码中有如下逻辑:
3、options中包含onTracklet events = []const onTrack = (e) => { events.push(e)}const obj = reactive({ foo: 1, bar: 2 })const runner = effect( () => { console.log(obj.foo) }, { onTrack })console.log('runner', runner)obj.foo++console.log("events", events)登录后复制
看下events的打印结果:
[ { effect: runner, // effect 函数的返回值 target: toRaw(obj), // 表示的是哪个响应式数据发生了变化 type: TrackOpTypes.GET, // 表示此次记录操作的类型。 get 表示获取值 key: 'foo' }]登录后复制二、源码分析1、effect方法的实现
// packages/reactivity/src/effect.tsexport interface ReactiveEffectOptions extends DebuggerOptions { lazy?: boolean scheduler?: EffectScheduler scope?: EffectScope allowRecurse?: boolean onStop?: () => void}export function effect<T = any>( fn: () => T, // 副作用函数 options?: ReactiveEffectOptions // 结构如上): ReactiveEffectRunner { // 如果 fn 对象上有 effect 属性 if ((fn as ReactiveEffectRunner).effect) { // 那么就将 fn 替换为 fn.effect.fn fn = (fn as ReactiveEffectRunner).effect.fn } // 创建一个响应式副作用函数 const _effect = new ReactiveEffect(fn) if (options) { // 将配置项合并到响应式副作用函数上 extend(_effect, options) // 如果配置项中有 scope 属性(该属性的作用是指定副作用函数的作用域) if (options.scope) recordEffectScope(_effect, options.scope) } if (!options || !options.lazy) { // options.lazy 不为true _effect.run() // 执行响应式副作用函数 首次执行fn() } // _effect.run作用域绑定到_effect const runner = _effect.run.bind(_effect) as ReactiveEffectRunner // 将响应式副作用函数赋值给 runner.effect runner.effect = _effect return runner}登录后复制
核心代码:
创建一个响应式副作用函数const _effect = new ReactiveEffect(fn)
,其运行结果如下:
非lazy状态执行响应式副作用函数_effect.run()
if (!options || !options.lazy) { // options.lazy 不为true _effect.run() // 执行响应式副作用函数 首次执行fn()}登录后复制
_effect.run
作用域绑定到_effect
// _effect.run作用域绑定到_effect const runner = _effect.run.bind(_effect) as ReactiveEffectRunner登录后复制
返回副作用函数runner
2、ReactiveEffect函数源码export class ReactiveEffect<T = any> { active = true deps: Dep[] = [] // 响应式依赖项的集合 parent: ReactiveEffect | undefined = undefined /** * Can be attached after creation * @internal */ computed?: ComputedRefImpl<T> /** * @internal */ allowRecurse?: boolean /** * @internal */ private deferStop?: boolean onStop?: () => void // dev only onTrack?: (event: DebuggerEvent) => void // dev only onTrigger?: (event: DebuggerEvent) => void constructor( public fn: () => T, public scheduler: EffectScheduler | null = null, scope?: EffectScope ) { // 记录当前 ReactiveEffect 对象的作用域 recordEffectScope(this, scope) } run() { // 如果当前 ReactiveEffect 对象不处于活动状态,直接返回 fn 的执行结果 if (!this.active) { return this.fn() } // 寻找当前 ReactiveEffect 对象的最顶层的父级作用域 let parent: ReactiveEffect | undefined = activeEffect let lastShouldTrack = shouldTrack // 是否要跟踪 while (parent) { if (parent === this) { return } parent = parent.parent } try { // 记录父级作用域为当前活动的 ReactiveEffect 对象 this.parent = activeEffect activeEffect = this // 将当前活动的 ReactiveEffect 对象设置为 “自己” shouldTrack = true // 将 shouldTrack 设置为 true (表示是否需要收集依赖) // effectTrackDepth 用于标识当前的 effect 调用栈的深度,执行一次 effect 就会将 effectTrackDepth 加 1 trackOpBit = 1 << ++effectTrackDepth if (effectTrackDepth <= maxMarkerBits) { // 初始依赖追踪标记 initDepMarkers(this) } else { // 清除依赖追踪标记 cleanupEffect(this) } // 返回副作用函数执行结果 return this.fn() } finally { // 如果 effect调用栈的深度 没有超过阈值 if (effectTrackDepth <= maxMarkerBits) { // 确定最终的依赖追踪标记 finalizeDepMarkers(this) } // 执行完毕会将 effectTrackDepth 减 1 trackOpBit = 1 << --effectTrackDepth // 执行完毕,将当前活动的 ReactiveEffect 对象设置为 “父级作用域” activeEffect = this.parent // 将 shouldTrack 设置为上一个值 shouldTrack = lastShouldTrack // 将父级作用域设置为 undefined this.parent = undefined // 延时停止,这个标志是在 stop 方法中设置的 if (this.deferStop) { this.stop() } } } stop() { // stopped while running itself - defer the cleanup // 如果当前 活动的 ReactiveEffect 对象是 “自己” // 延迟停止,需要执行完当前的副作用函数之后再停止 if (activeEffect === this) { // 在 run 方法中会判断 deferStop 的值,如果为 true,就会执行 stop 方法 this.deferStop = true } else if (this.active) {// 如果当前 ReactiveEffect 对象处于活动状态 cleanupEffect(this) // 清除所有的依赖追踪标记 if (this.onStop) { this.onStop() } this.active = false // 将 active 设置为 false } }}登录后复制
run方法的作用就是执行副作用函数,并且在执行副作用函数的过程中,会收集依赖;
stop方法的作用就是停止当前的ReactiveEffect对象,停止之后,就不会再收集依赖了;
activeEffect和this并不是每次都相等的,因为activeEffect会跟着调用栈的深度而变化,而this则是固定的;
三、依赖收集相关1、如何触发依赖收集在副作用函数中, obj.count
就会触发依赖收集
// 先执行fun() // 打印出1const runner = new ReactiveEffect(fn)return runnerrunner: { run() { this.fun() //执行fun }, stop() { }}0登录后复制
触发的入口在get拦截器里面
// 先执行fun() // 打印出1const runner = new ReactiveEffect(fn)return runnerrunner: { run() { this.fun() //执行fun }, stop() { }}1登录后复制2、track源码
// 先执行fun() // 打印出1const runner = new ReactiveEffect(fn)return runnerrunner: { run() { this.fun() //执行fun }, stop() { }}2登录后复制
shouldTrack在上面也讲过,它的作用就是控制是否收集依赖;
activeEffect就是我们刚刚讲的ReactiveEffect对象,它指向的就是当前正在执行的副作用函数;
track方法的作用就是收集依赖,它的实现非常简单,就是在targetMap中记录下target和key;
targetMap是一个WeakMap,它的键是target,值是一个Map,这个Map的键是key,值是一个Set;
targetMap的结构伪代码如下:
// 先执行fun() // 打印出1const runner = new ReactiveEffect(fn)return runnerrunner: { run() { this.fun() //执行fun }, stop() { }}3登录后复制
以上是最原始的depMap
dev环境为增加响应式调试会增加eventInfo
// 先执行fun() // 打印出1const runner = new ReactiveEffect(fn)return runnerrunner: { run() { this.fun() //执行fun }, stop() { }}4登录后复制
eventInfo结构如下:
trackEffects(dep, eventInfo)
如果 dep 中没有当前的 ReactiveEffect 对象,就会添加进去, 作用就把对象的属性操作与副作用函数建立关联,接下来看trackEffects
// 先执行fun() // 打印出1const runner = new ReactiveEffect(fn)return runnerrunner: { run() { this.fun() //执行fun }, stop() { }}5登录后复制
dep.add(activeEffect!)
如果 dep 中没有当前的 ReactiveEffect 对象,就会添加进去
最终生成的depTarget结构如下:
四、触发依赖比如例子中代码obj.count++
就会触发set拦截,触发依赖更新
// 先执行fun() // 打印出1const runner = new ReactiveEffect(fn)return runnerrunner: { run() { this.fun() //执行fun }, stop() { }}6登录后复制1、trigger依赖更新
// 先执行fun() // 打印出1const runner = new ReactiveEffect(fn)return runnerrunner: { run() { this.fun() //执行fun }, stop() { }}7登录后复制
const depsMap = targetMap.get(target)
获取 targetMap 中的 depsMap targetMap结构如下:
执行以上语句之后的depsMap结构如下:
将 depsMap 中 key 对应的 ReactiveEffect 对象添加到 deps 中deps.push(depsMap.get(key))
之后的deps结构如下:
triggerEffects(deps[0], eventInfo)
// 先执行fun() // 打印出1const runner = new ReactiveEffect(fn)return runnerrunner: { run() { this.fun() //执行fun }, stop() { }}8登录后复制
trigger函数的作用就是触发依赖,当我们修改数据的时候,就会触发依赖,然后执行依赖中的副作用函数。
在这里的实现其实并没有执行,主要是收集一些需要执行的副作用函数,然后在丢给triggerEffects函数去执行,接下来看看triggerEffects函数。
2、triggerEffects(deps[0], eventInfo)// 先执行fun() // 打印出1const runner = new ReactiveEffect(fn)return runnerrunner: { run() { this.fun() //执行fun }, stop() { }}9登录后复制
主要步骤
const effects = isArray(dep) ? dep : [...dep]
获取effects
triggerEffect(effect, debuggerEventExtraInfo)
执行effect,接下来看看源码
() => { console.log(obj.count)}0登录后复制
effect.run()就是执行副作用函数
以上就是Vue3响应式核心之effect怎么使用的详细内容,更多请关注9543建站博客其它相关文章!
发表评论