前言
说起 effectScope
,我第一次见到还是在vueuse
的源码中,还不太理解,大佬当时也讲解了很多:
就在刚刚大佬也是出了期视频小小的讲解了一下,本文章结合大佬的讲解视频,以及源码来对视频进行总结文章源自灵鲨社区-https://www.0s52.com/bcjc/vue-jsjc/16134.html
开始
先看一个案例: 使用 effect 得方式写一个累加器,并使用一个暂停按钮,实现暂停后,累加器再点击加减无效文章源自灵鲨社区-https://www.0s52.com/bcjc/vue-jsjc/16134.html
html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/3.4.29/vue.global.min.js"></script>
<style>
#app {
margin: 100px 300px;
}
button {
background: #1a1a1a;
color: #fff;
}
</style>
</head>
<body>
<div id="app">
<div>counter: <span class="counter"></span></div>
<div>doubled: <span class="doubled"></span></div>
<button class="increment">+</button>
<button class="decrement">-</button>
<button class="stop">停止</button>
</div>
<script>
const { watch, watchEffect, ref, effect, effectScope, computed } = Vue
// effectScope
const counterEl = document.querySelector('.counter')
const doubledEl = document.querySelector('.doubled')
const incrementBtn = document.querySelector('.increment')
const decrementBtn = document.querySelector('.decrement')
const stopBtn = document.querySelector('.stop')
const counter = ref(0)
const cleanups = []
watchEffect(() => {
counterEl.textContent = counter.value
})
watch(counter, () => {
console.log("使用 watch,监听counter变化")
})
effect(() => {
doubledEl.textContent = counter.value * 2
})
incrementBtn.addEventListener('click', () => {
counter.value++
})
decrementBtn.addEventListener('click', () => {
counter.value--
})
stopBtn.addEventListener('click', () => {
// 点击停止,让累加器不在变化
})
</script>
</body>
</html>
这是分别使用了 watchEffect 和 effect
来改变html上的值,这时候在这个代码实现停止功能的话,需要把每个effect
清除掉文章源自灵鲨社区-https://www.0s52.com/bcjc/vue-jsjc/16134.html
js
const stopWatchEffect = watchEffect(() => {
counterEl.textContent = counter.value
})
const stopWatch = watch(counter, () => {
console.log("使用 watch,监听counter变化")
})
const {effect: effectInstance} = effect(() => {
doubledEl.textContent = counter.value * 2
})
cleanups.push(stopWatchEffect, stopWatch, effectInstance.stop.bind(effectInstance))
incrementBtn.addEventListener('click', () => {
counter.value++
})
decrementBtn.addEventListener('click', () => {
counter.value--
})
stopBtn.addEventListener('click', () => {
// 点击停止,让累加器不在变化
cleanups.forEach(clean => clean())
})
这样清除后,再去点击增减的时候,就没有依赖执行了文章源自灵鲨社区-https://www.0s52.com/bcjc/vue-jsjc/16134.html
接下来使用 effectScope
来实现这个需求文章源自灵鲨社区-https://www.0s52.com/bcjc/vue-jsjc/16134.html
effectScope 源码
在使用它实现这个需求前,需要知道它到底是啥,直接翻源码看下文章源自灵鲨社区-https://www.0s52.com/bcjc/vue-jsjc/16134.html
ts
export class EffectScope {
/**
* @internal
*/
private _active = true
/**
* @internal
*/
effects: ReactiveEffect[] = []
/**
* @internal
*/
cleanups: (() => void)[] = []
/**
* only assigned by undetached scope
* @internal
*/
parent: EffectScope | undefined
/**
* record undetached scopes
* @internal
*/
scopes: EffectScope[] | undefined
/**
* track a child scope's index in its parent's scopes array for optimized
* removal
* @internal
*/
private index: number | undefined
constructor(public detached = false) {
this.parent = activeEffectScope
if (!detached && activeEffectScope) {
this.index =
(activeEffectScope.scopes || (activeEffectScope.scopes = [])).push(
this,
) - 1
}
}
get active() {
return this._active
}
run<T>(fn: () => T): T | undefined {
if (this._active) {
const currentEffectScope = activeEffectScope
try {
activeEffectScope = this
return fn()
} finally {
activeEffectScope = currentEffectScope
}
} else if (__DEV__) {
warn(`cannot run an inactive effect scope.`)
}
}
/**
* This should only be called on non-detached scopes
* @internal
*/
on() {
activeEffectScope = this
}
/**
* This should only be called on non-detached scopes
* @internal
*/
off() {
activeEffectScope = this.parent
}
stop(fromParent?: boolean) {
if (this._active) {
let i, l
for (i = 0, l = this.effects.length; i < l; i++) {
this.effects[i].stop()
}
for (i = 0, l = this.cleanups.length; i < l; i++) {
this.cleanups[i]()
}
if (this.scopes) {
for (i = 0, l = this.scopes.length; i < l; i++) {
this.scopes[i].stop(true)
}
}
// nested scope, dereference from parent to avoid memory leaks
if (!this.detached && this.parent && !fromParent) {
// optimized O(1) removal
const last = this.parent.scopes!.pop()
if (last && last !== this) {
this.parent.scopes![this.index!] = last
last.index = this.index!
}
}
this.parent = undefined
this._active = false
}
}
}
export function effectScope(detached?: boolean) {
return new EffectScope(detached)
}
可以看到 effectScope
就是一个工厂函数,返回了一个 EffectScope
的实例,虽然这个 class EffectScope 看起来很长,如果一步一步看,其实也并没有多少东西,大致这个实例就是有 run,off,on,stop
方法,以及一些参数,暂时不用管,可以看到 stop 方法中 cleanups
部分,将这个数组中的函数依次执行,这和我们第一次实现暂停一模一样,就是收集了所有 effect 的 stop 放在这个数组中,需要暂停时,执行一遍即可。文章源自灵鲨社区-https://www.0s52.com/bcjc/vue-jsjc/16134.html
结合大佬给我讲解的记录来看,他会有嵌套的 scope,所以在 stop函数中,也全部执行了一次 scope 的 stop 函数,递归清除,也可以嵌套 effect,所以在收集的effect也是执行了stop,大致就了解这些吧,源码比较详细,可以自己多试试。文章源自灵鲨社区-https://www.0s52.com/bcjc/vue-jsjc/16134.html
使用effectScope
对effectScope
一定了解后,开始使用effectScope
来实现这个需求文章源自灵鲨社区-https://www.0s52.com/bcjc/vue-jsjc/16134.html
js
const scope = effectScope()
scope.run(() => {
watchEffect(() => {
counterEl.textContent = counter.value
})
watch(counter, () => {
console.log("使用 watch,监听counter变化")
})
effect(() => {
doubledEl.textContent = counter.value * 2
})
})
incrementBtn.addEventListener('click', () => {
counter.value++
})
decrementBtn.addEventListener('click', () => {
counter.value--
})
stopBtn.addEventListener('click', () => {
// 点击停止,让累加器不在变化
scope.stop()
})
在 run 函数中,会自动收集 effect,scope,stop函数等,收集到这个scope中,执行stop函数的时候,会执行内部的每个effect.stop,cleanups收集的stop还有递归执行scope.stop。文章源自灵鲨社区-https://www.0s52.com/bcjc/vue-jsjc/16134.html
在 vueuse
中大量使用了effectScope
,可以挑个看下
effectScope在vueuse的运用
ts
export function createGlobalState<Fn extends AnyFn>(
stateFactory: Fn,
): Fn {
let initialized = false
let state: any
const scope = effectScope(true)
return ((...args: any[]) => {
if (!initialized) {
state = scope.run(() => stateFactory(...args))!
initialized = true
}
return state
}) as Fn
}
创建了一个单独的 scope,初始化时收集依赖,并返回了参数函数的返回值。具体使用就不过多说明了,可以去vueuse中查看相关代码。
总结
- effectScope 在平时写代码中基本不会使用到,只有封装一个库等,提供给别人使用的工具,就回经常使用到。
- effectScope 就是提供了一个给用户对上下文进行简单操作的方式。
评论