说明:我用的这份源码呢是3.0.11,是很早的vue3的版本,后面新的vue源码呢我也看了一些,我们主要会用的源码大多都是小改小动,做了一些更好的封装。所以我认为目前我的这份老源码也是可以读的,哈哈哈哈
源码在packages/reactivity/src/effect.ts
effect作用
effect是vue3实现响应式的核心。
effect的执行过程:他会包裹一个函数,让被包裹的函数成为一个待收集的函数,而这个被包裹的函数在执行之时,函数内部的响应式数据触发getter操作,此时就会收集待收集的函数,于是便建立起响应式数据和函数之间的依赖关系,后续响应式数据发生改变便会触发依赖、函数执行。
正文
还是先看源码,下面是定义部分
export interface ReactiveEffectOptions {
lazy?: boolean//是否延迟触发
scheduler?: (job: ReactiveEffect) => void//调度函数(这个调度函数有很多都是加入队列,然后异步执行)
onTrack?: (event: DebuggerEvent) => void//收集依赖时的回调函数(用于调试)
onTrigger?: (event: DebuggerEvent) => void//触发依赖时的回调函数(用于调试)
onStop?: () => void//停止时的回调函数
allowRecurse?: boolean//是否允许递归
}
/*
effect 接收两个参数
fn 回调函数
options 参数
*/
export function effect<T = any>(
fn: () => T,
options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
if (isEffect(fn)) {
// 如果已经是 effect 先重置为原始对象
fn = fn.raw
}
//创建effect
const effect = createReactiveEffect(fn, options)
if (!options.lazy) {
//配置里没有传入lazy,直接执行一次,执行过程中创建依赖关系,收集依赖关系。
effect()
}
return effect
}
effcet入口也就那样,好像没什么大不了的,简简单单!下面我们来看effect是如何创建的。
function createReactiveEffect<T = any>(
fn: () => T,
options: ReactiveEffectOptions
): ReactiveEffect<T> {
const effect = function reactiveEffect(): unknown {
//如果我们调用了effect.stop,那么effect失活
if (!effect.active) {
//如果没有调度者,直接返回,否者直接执行并返回结果
return options.scheduler ? undefined : fn()
}
//判断effectStack中有没有effect, 如果在则不处理
if (!effectStack.includes(effect)) {
//清除effect依赖
cleanup(effect)
try {
//开始收集依赖
enableTracking()
//入栈
effectStack.push(effect)
//将activeEffect置为effect,方便后续操作进行依赖收集
activeEffect = effect
//这里执行了传入函数fn,在执行fn的过程中。里面的响应式数据就会收activeEffect
return fn()
} finally {
//出栈
effectStack.pop()
//重置依赖
resetTracking()
//重置activeEffect
activeEffect = effectStack[effectStack.length - 1]
}
}
} as ReactiveEffect
effect.id = uid++
effect.allowRecurse = !!options.allowRecurse
effect._isEffect = true
effect.active = true
effect.raw = fn
effect.deps = []
effect.options = options
return effect
}
// 每次 effect 运行都会重新收集依赖, deps 是 effect 的依赖数组, 需要全部清空
function cleanup(effect: ReactiveEffect) {
const { deps } = effect
if (deps.length) {
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect)
}
deps.length = 0
}
}
上面的函数中effectStack存在的意义在于,effect包裹的函数体里面也有effect,这样全局的activeEffect就会丢失。
为什么要cleanup,因为每次effect执行之时,它的依赖也许会发生改变。打个比方:我们都知道渲染函数执行,生成vnode,函数内部响应式数据触发getter,触发依赖收集,假如我使用了v-if切换渲染两个不同的列表,第一次执行effect就依赖第一次列表的数据,第二次执行effect就依赖第二次列表的数据(因为数据在getter被触发时才会收集依赖)。
track收集依赖
响应式数据被访问之时(也就是触发响应式getter操作),track函数会被调用,此时收集依赖。
export function track(target: object, type: TrackOpTypes, key: unknown) {
if (!shouldTrack || activeEffect === undefined) {
return
}
// 每个target会对应一个depsMap
let depsMap = targetMap.get(target)
// 判断是否直接可以找到一个depsMap
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
// 每个key在depsMap中对应一个dep集合
let dep = depsMap.get(key)
// 判断之前是否有这个集合
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
if (!dep.has(activeEffect)) {
// 手机当前激活的effect作为依赖
dep.add(activeEffect)
// 将收集的依赖放入到activeEffect的deps中
activeEffect.deps.push(dep)
if (__DEV__ && activeEffect.options.onTrack) {
// 开发环境会触发onTrack, 仅用于调试
activeEffect.options.onTrack({
effect: activeEffect,
target,
type,
key
})
}
}
}
export const enum TrackOpTypes {
GET = 'get',
HAS = 'has',
ITERATE = 'iterate'
}
track的代码比较简单,主要的难点就是targetMap(是一个weakMap,保存着每个对象和depsMap的映射关系)这个数据结构,targetMap里面保存着所有的依赖关系。拿对象打比方:每个对象的每个key都有自己的依赖函数(可能是多个依赖函数,如果有多个函数都用到了此数据),那么对象的某个key对应的值发生了改变,我们如何拿到他的依赖函数呢,我们就先从targetMap拿到这个对象的所有依赖depsMap(是一个map,保存着每个key对应的依赖函数),再从depsMap根据key拿到具体的dep(是一个set,因为可能有多个依赖函数,set也避免重复添加)。
trigger触发依赖
我们都知道响应式数据的改变,会触发依赖,下面将详细解析触发过程发生了什么。
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
// 通过targetMap拿到target对应的依赖集合
const depsMap = targetMap.get(target)
if (!depsMap) {
// never been tracked
return
}
// 创建一个effects的集合,用来保存即将执行的依赖函数
const effects = new Set<ReactiveEffect>()
// 添加effects的函数
const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect || effect.allowRecurse) {
//这里是避免造成无限循环,因为如果在执行依赖函数时改变某个响应式数据,
//又会触发此依赖函数(基本依赖函数不会改变响应式数据,一般都是些渲染函数,
//渲染函数仅用来生成vnode)
effects.add(effect)
}
})
}
}
//从这里开始到run函数之前,都是判断响应式数据改变的方式,然后拿到对应的依赖的函数,添加到effects
if (type === TriggerOpTypes.CLEAR) {
// collection being cleared
// trigger all effects for target
//set或者map数据的clear,触发set/map的所有依赖
depsMap.forEach(add)
} else if (key === 'length' && isArray(target)) {
//数组长度改变
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= (newValue as number)) {
add(dep)
}
})
} else {
// schedule runs for SET | ADD | DELETE
if (key !== void 0) {
//viod 0 通常就是指的 undefined
//对象具体的某个key对应的属性发生改变
add(depsMap.get(key))
}
// also run for iteration key on ADD | DELETE | Map.SET
// 判断是ADD,还是DELETE, 还是SET, 添加不同的集合
switch (type) {
case TriggerOpTypes.ADD:
if (!isArray(target)) {
//
add(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
} else if (isIntegerKey(key)) {
// new index added to array -> length changes
add(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
case TriggerOpTypes.SET:
if (isMap(target)) {
add(depsMap.get(ITERATE_KEY))
}
break
}
}
// 定义执行的函数
const run = (effect: ReactiveEffect) => {
if (__DEV__ && effect.options.onTrigger) {
//调试时执行
effect.options.onTrigger({
effect,
target,
key,
type,
newValue,
oldValue,
oldTarget
})
}
// 调用执行
if (effect.options.scheduler) {
//如果有调度者,就传给调度者执行
effect.options.scheduler(effect)
} else {
// 直接执行
effect()
}
}
// 遍历所有的effects执行run函数
effects.forEach(run)
}
export const enum TriggerOpTypes {
SET = 'set',
ADD = 'add',
DELETE = 'delete',
CLEAR = 'clear'
}
个人拙见,如有错误,希望指出,谢谢!