您好,欢迎访问一九零五行业门户网

Vue3响应式核心之effect怎么使用

通常情况下我们是不会直接使用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就会触发依赖收集
const runner = effect(() => { console.log(obj.count) })
触发的入口在get拦截器里面
function creategetter(isreadonly = false, shallow = false) { // 闭包返回 get 拦截器方法 return function get(target: target, key: string | symbol, receiver: object) { // ... if (!isreadonly) { track(target, trackoptypes.get, key) } // ... }
2、track源码const targetmap = new weakmap();/** * 收集依赖 * @param target target 触发依赖的对象,例子中的obj * @param type 操作类型 比如obj.count就是get * @param key 指向对象的key, 比如obj.count就是count */export function track(target: object, type: trackoptypes, key: unknown) { if (shouldtrack && activeeffect) { // 是否应该依赖收集 & 当前的new reactiveeffect()即指向的就是当前正在执行的副作用函数 // 如果 targetmap 中没有 target,就会创建一个 map let depsmap = targetmap.get(target) if (!depsmap) { targetmap.set(target, (depsmap = new map())) } let dep = depsmap.get(key) if (!dep) { depsmap.set(key, (dep = createdep())) // createdep 生成dep = { w:0, n: 0} } const eventinfo = __dev__ ? { effect: activeeffect, target, type, key } : undefined trackeffects(dep, eventinfo) }}
shouldtrack在上面也讲过,它的作用就是控制是否收集依赖;
activeeffect就是我们刚刚讲的reactiveeffect对象,它指向的就是当前正在执行的副作用函数;
track方法的作用就是收集依赖,它的实现非常简单,就是在targetmap中记录下target和key;
targetmap是一个weakmap,它的键是target,值是一个map,这个map的键是key,值是一个set;
targetmap的结构伪代码如下:
targetmap = { target: { key: dep }, // 比如: obj: { count: { w: 0, n: 0 } }}
以上是最原始的depmap
dev环境为增加响应式调试会增加eventinfo
const eventinfo = __dev__ ? { effect: activeeffect, target, type, key } : undefined
eventinfo结构如下:
trackeffects(dep, eventinfo)
如果 dep 中没有当前的 reactiveeffect 对象,就会添加进去, 作用就把对象的属性操作与副作用函数建立关联,接下来看trackeffects
3、trackeffects(dep, eventinfo)源码解读export function trackeffects( dep: dep, debuggereventextrainfo?: debuggereventextrainfo) { let shouldtrack = false if (effecttrackdepth <= maxmarkerbits) { if (!newtracked(dep)) { // 执行之前 dep = set(0) {w: 0, n: 0} // 执行之后 dep = set(0) {w: 0, n: 2} dep.n |= trackopbit // set newly tracked shouldtrack = !wastracked(dep) } } else { // full cleanup mode. shouldtrack = !dep.has(activeeffect!) } if (shouldtrack) { // 将activeeffect添加到dep dep.add(activeeffect!) activeeffect!.deps.push(dep) if (__dev__ && activeeffect!.ontrack) { // ontrack逻辑 activeeffect!.ontrack( extend( { effect: activeeffect! }, debuggereventextrainfo! ) ) } }}
dep.add(activeeffect!) 如果 dep 中没有当前的 reactiveeffect 对象,就会添加进去
最终生成的deptarget结构如下:
四、触发依赖比如例子中代码obj.count++就会触发set拦截,触发依赖更新
function createsetter(shallow = false) { return function set( target: object, key: string | symbol, value: unknown, receiver: object ): boolean { //... const result = reflect.set(target, key, value, receiver) // don't trigger if target is something up in the prototype chain of original if (target === toraw(receiver)) { if (!hadkey) { trigger(target, triggeroptypes.add, key, value) // 触发add依赖更新 } else if (haschanged(value, oldvalue)) { trigger(target, triggeroptypes.set, key, value, oldvalue) //触发set依赖更新 } } //... }
1、trigger依赖更新// 路径:packages/reactivity/src/effect.tsexport function trigger( target: object, type: triggeroptypes, key?: unknown, newvalue?: unknown, oldvalue?: unknown, oldtarget?: map<unknown, unknown> | set<unknown>) { const depsmap = targetmap.get(target) // 获取depsmap, targetmap是在track中创建的依赖 if (!depsmap) { // never been tracked return } let deps: (dep | undefined)[] = [] if (type === triggeroptypes.clear) { // collection being cleared // trigger all effects for target deps = [...depsmap.values()] } else if (key === 'length' && isarray(target)) { const newlength = number(newvalue) depsmap.foreach((dep, key) => { if (key === 'length' || key >= newlength) { deps.push(dep) } }) } else { // schedule runs for set | add | delete if (key !== void 0) { deps.push(depsmap.get(key)) } // also run for iteration key on add | delete | map.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 } } const eventinfo = __dev__ ? { target, type, key, newvalue, oldvalue, oldtarget } : undefined if (deps.length === 1) { if (deps[0]) { if (__dev__) { triggereffects(deps[0], eventinfo) } else { triggereffects(deps[0]) } } } else { const effects: reactiveeffect[] = [] for (const dep of deps) { if (dep) { effects.push(...dep) } } if (__dev__) { triggereffects(createdep(effects), eventinfo) } else { triggereffects(createdep(effects)) } }}
const depsmap = targetmap.get(target) 获取 targetmap 中的 depsmap targetmap结构如下:
执行以上语句之后的depsmap结构如下:
将 depsmap 中 key 对应的 reactiveeffect 对象添加到 deps 中deps.push(depsmap.get(key))之后的deps结构如下:
triggereffects(deps[0], eventinfo)
const eventinfo = __dev__ ? { target, type, key, newvalue, oldvalue, oldtarget } : undefined if (deps.length === 1) { if (deps[0]) { if (__dev__) { triggereffects(deps[0], eventinfo) } else { triggereffects(deps[0]) } } }
trigger函数的作用就是触发依赖,当我们修改数据的时候,就会触发依赖,然后执行依赖中的副作用函数。
在这里的实现其实并没有执行,主要是收集一些需要执行的副作用函数,然后在丢给triggereffects函数去执行,接下来看看triggereffects函数。
2、triggereffects(deps[0], eventinfo)export function triggereffects( dep: dep | reactiveeffect[], debuggereventextrainfo?: debuggereventextrainfo) { // spread into array for stabilization const effects = isarray(dep) ? dep : [...dep] for (const effect of effects) { if (effect.computed) { triggereffect(effect, debuggereventextrainfo) } } for (const effect of effects) { if (!effect.computed) { triggereffect(effect, debuggereventextrainfo) } }}
主要步骤
const effects = isarray(dep) ? dep : [...dep]获取effects
triggereffect(effect, debuggereventextrainfo)执行effect,接下来看看源码
3、triggereffect(effect, debuggereventextrainfo)function triggereffect( effect: reactiveeffect, debuggereventextrainfo?: debuggereventextrainfo) { if (effect !== activeeffect || effect.allowrecurse) { // 如果 effect.ontrigger 存在,就会执行,只有开发模式下才会执行 if (__dev__ && effect.ontrigger) { effect.ontrigger(extend({ effect }, debuggereventextrainfo)) } // 如果 effect 是一个调度器,就会执行 scheduler if (effect.scheduler) { effect.scheduler() } else { // 其它情况执行 effect.run() effect.run() } }}
effect.run()就是执行副作用函数
以上就是vue3响应式核心之effect怎么使用的详细内容。
其它类似信息

推荐信息