引言
书接上回,上回我们梳理了解了reactive的核心内部原理,这次我们聊聊effect的相关内容,探寻下它又有哪些神奇之处呢.....
副作用
effect全称叫side effect,副作用。
什么是副作用呢,一个函数运行后产生了可以影响其外部或可以看到的效果,就叫副作用,比如console.log,document. Body. Append,alert再或者是showModel(在页面中展示一个弹层),或者window.open打开一个新窗口。
哪些函数没有副作用呢,只用来计算结果的函数,比如Math.max,JSON.parse,它们的运行除了返回结果外不会有其它效果,这就叫不产生副作用。
基本上可以简化的理解为副作用就是执行某种操作,无副作用就是执行某种计算
基础使用
首先看下面 effect 的传参,fn 是回调函数,options 是传入的参数。
export function effect<T = any>(
fn: () => T,
options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
if (isEffect(fn)) {
fn = fn.raw
}
const effect = createReactiveEffect(fn, options)
if (!options.lazy) {
effect()
}
return effect
}
其中 option 的参数如下,都是属于可选的。
参数
含义
lazy
是否延迟触发 effect
computed
是否为计算属性
scheduler
调度函数
onTrack
追踪时触发
onTrigger
触发回调时触发
onStop
停止监听时触发
export interface ReactiveEffectOptions {
lazy?: boolean
computed?: boolean
scheduler?: (job: ReactiveEffect) => void
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
onStop?: () => void
}
代码实现
const rea = reactive({
a: 1,
b: 2,
c: 3
})
// 1, 2
effect(() => {
console.log(rea.a, rea.b)
})
setTimeout(()=>{
rea.a = 2
})
上面的的小例子会输出1,2,effect默认会执行一次,当其中的依赖数据变化了,会重新再次执行
原理如下:
// 主要是用栈的形式维护当前执行的effect和历史effect的关系let effectStack = []let activeEffect;class ReactiveEffect { //标识当前是否被激活 flagActive = true; //依赖收集的数组 deps = [] // 涉及effect依赖哪些属性、哪些属性有相关effect的多对多关系 constructor(public fn) { } run () { if (!this.flagActive) { this.fn() } try { effectStack.push(activeEffect = this) // 执行相关回调函数,会执行 reactive中的get函数 this.fn() } finally { effectStack.pop() // 执行完上一个effect,出栈,获取最新的栈顶数据 activeEffect = effectStack[effectStack.length - 1] } }}/** * 提供给reactive中取值问题*/export function track (target, key) {}export function effect (fn) { const _effect = new ReactiveEffect(fn); _effect.run()}
验证case 1
effect(() => {
console.log(rea.a)
effect(() => {
console.log(rea.b)
})
console.log(rea.c)
})
执行过程:
1、effectStack = [最外层effect] activeEffect = 最外层effect
2、effectStack = [最外层effect, 内部effect] activeEffect = 内部effect
3、内部effect: 收集 rea.b的属性
4、内部effect完成,出栈:effectStack = [最外层effect] activeEffect = 最外层effect
5、最终:内部effect: 收集 rea.b的属性, 最外层effect: 收集 rea.a 和 rea.c 的属性
循环问题
effect在什么情况下触发呢?effect会默认执行一次,同时当前effect收集的属性发生变化时会再次触发,我们看下面的示例:
case 2:
const rea = reactive({
a: 1,
b: 2
})
effect(()=>{
rea.a +=1 //会怎么样
})
上面出现的情况很明显,就是effectStack中一直在追加当前的effect出现死循环,针对这种情况我们需要兼容下,如果当前effectStack中已经存在当前的 effect 对象就不需要再次触发了
// 主要是用栈的形式维护当前执行的effect和历史effect的关系let effectStack = []let activeEffect;class ReactiveEffect { //标识当前是否被激活 flagActive = true; //依赖收集的数组 deps = [] // 涉及effect依赖哪些属性、哪些属性有相关effect的多对多关系 constructor(public fn) { } run () { if (!this.flagActive) { this.fn() } // 判断是否已经存在当前effect --start if(!effectStack.includes(this)){ // todo } // 判断是否已经存在当前effect --end }}
effect与属性结构关系维护
同一个属性对应多个effect应该如何管理维护?
case 3:
const rea = reactive({
a: 1,
b: 2
})
effect1(() => {
console.log(rea.a)
})
effect2(() => {
console.log(rea.a)
})
因为 effect与依赖属性是多对多的关系,需要在track函数中处理这种关系:
-
一个属性对应多个effect
-
一个effect可能收集多个属性
最终代码如下
// 主要是用栈的形式维护当前执行的effect和历史effect的关系
let effectStack = []
let activeEffect;
const targetMap = new WeakMap()
class ReactiveEffect {
//标识当前是否被激活
flagActive = true;
//依赖收集的数组
deps = []
// 涉及effect依赖哪些属性、哪些属性有相关effect的多对多关系
constructor(public fn) {
}
run() {
if (!this.flagActive) {
this.fn()
}
// 判断是否已经存在当前effect
if (!effectStack.includes(this)) {
try {
effectStack.push(activeEffect = this)
// 执行相关回调函数,会执行 reactive中的get函数
this.fn()
} finally {
effectStack.pop()
// 执行完上一个effect,出栈,获取最新的栈顶数据
activeEffect = effectStack[effectStack.length - 1]
}
}
}
}
/**
* @description 是否需要跟踪收集
* @return {Boolean} 是否需要收集标识
*/
export function isTracking() {
return activeEffect !== undefined
}
/**
* 提供给reactive中取值问题 get调用
*/
export function track(target, key) {
if (!isTracking()) {
return
}
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
let needTracked = !dep.has(activeEffect)
if (needTracked) {
// 一个属性对应多个effect
dep.add(activeEffect)
// 一个effect可能收集多个属性
activeEffect.deps.push(dep)
}
}
/**
* @description 提供reactive中set调用
*/
export function trigger(target, key) {
let depsMap = targetMap.get(target)
// 修改的属性没有依赖,直接返回
if (!depsMap) {
return
}
// 存放 set集合
let deps = []
if (key !== undefined) {
deps.push(depsMap.get(key))
}
let effects = []
for (const dep of deps) {
effects.push(...dep)
}
for (const effect of effects) {
if (effect !== activeEffect) {
effect.run()
}
}
}
export function effect(fn) {
const _effect = new ReactiveEffect(fn);
_effect.run()
}
针对vue3中effect的解析和实现,暂时告一段落。
未完待续.....