我们知道vue3提供了响应式函数方法如reactive,ref,我们可以通过reactive或者ref创建一个响应式变量,然后在setup方法内返回变量值,然后我们就可以在template模板中使用该响应式变量;当响应式变量值被更新的时候,对应的模板值会被更新。
那么,vue是怎么在某一个响应式变量值更新的时候,触发页面的更新操作呢?这就是本篇文章带大家继续深入了解的响应式原理部分 依赖收集和通知更新
在上一篇文章中# Vue3源码解析--reactive篇和# Vue3源码解析--ref篇我们了解到,当响应式变量值被读取,会进行对应的依赖收集,在响应式变量值被修改时,会进行通知更新操作。
1. 依赖收集
当响应式变量值被获取的时候,会进行对应的依赖收集
// 收集方式类型枚举定义
export const enum TrackOpTypes {
GET = 'get',
HAS = 'has',
ITERATE = 'iterate'
}
get value() {
// 依赖收集
track(toRaw(this), TrackOpTypes.GET, 'value')
return this._value
}
track()方法接受三个参数:被收集对象,收集类型,key
// targetMap 依赖管理中心,用于收集依赖和触发依赖
const targetMap = new WeakMap<any, KeyToDepMap>()
// 如果存在多个effect,则依次放入栈中
const effectStack: ReactiveEffect[] = []
// 存放当前活动effect副作用函数
let activeEffect: ReactiveEffect | undefined
// track的主要作用是将响应式副作用函数,添加为对应target.key保存的set结构中
// 当被通知更新阶段,再依次取出执行
export function track(target: object, type: TrackOpTypes, key: unknown) {
// 依赖收集进行的前置条件:
if (!shouldTrack || activeEffect === undefined) {
return
}
// targetMap大概样子
// targetMap(weakmap) = {
// target1: map{
// key1: set(fn1,fn2,fn3...)
// key2: set(fn1,fn2,fn3...)
// },
// target2: map{
// key1: set(fn1,fn2,fn3...)
// key2: set(fn1,fn2,fn3...)
// },
// }
// 根据target对象取出当前target对应的depsMap结构
let depsMap = targetMap.get(target)
if (!depsMap) {
// 第一次收集依赖可能不存在
targetMap.set(target, (depsMap = new Map()))
}
// 根据key取出对应的用于存储依赖的Set集合
let dep = depsMap.get(key)
if (!dep) {
// 第一次可能不存在
depsMap.set(key, (dep = new Set()))
}
if (!dep.has(activeEffect)) {
// 将当前的副作用函数收集进依赖中
dep.add(activeEffect)
// 并在当前副作用函数的 deps 属性中记录该依赖
activeEffect.deps.push(dep)
}
}
2. effect
track进行依赖收集,主要是收集目标target key被读取对应的副作用函数,effect就是一个将fn函数包装为副作用函数的方法。
我们先看下effect相关的类型定义,有助于我们理解effect实现
// 响应式副作用函数的类型定义
export interface ReactiveEffect<T = any> {
(): T
_isEffect: true // 属性标识这是一个副作用
id: number // 序号id,标识effect唯一性
active: boolean // 标识这个副作用启用和停用的状态
raw: () => T // 保存初始传入的函数
deps: Array<Dep> // 这个副作用的所有依赖
options: ReactiveEffectOptions
allowRecurse: boolean
}
// effect第二个参数options类型定义
export interface ReactiveEffectOptions {
lazy?: boolean
scheduler?: (job: ReactiveEffect) => void
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
onStop?: () => void
allowRecurse?: boolean
}
// effect
export function effect<T = any>(
fn: () => T,
options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
// 如果传入参数已经是effect包装的响应式副作用函数,则获取副作用函数保存的原始值函数
if (isEffect(fn)) {
fn = fn.raw
}
const effect = createReactiveEffect(fn, options) // 返回一个响应式的effect函数
if (!options.lazy) {
// 非惰性的,默认会执行一次。
effect()
}
return effect
}
// createReactiveEffect
// 如果存在多个effect,则依次放入栈中
const effectStack: ReactiveEffect[] = []
// 存放当前活动effect副作用函数
let activeEffect: ReactiveEffect | undefined
// 序号,每次执行createReactiveEffect执行++1
let uid = 0
function createReactiveEffect<T = any>(
fn: () => T,
options: ReactiveEffectOptions
): ReactiveEffect<T> {
const effect = function reactiveEffect(): unknown {
if (!effectStack.includes(effect)) {
// 保证effect不会被重复保存在effectStack
cleanup(effect)
try {
enableTracking()
// 在取值之前将当前effect放到栈顶
effectStack.push(effect)
// 全局变量保存当前effect
activeEffect = effect
// 执行effect的回调就是一个取值的过程
return fn()
} finally {
// 从effectStack栈顶将自己移除
effectStack.pop()
resetTracking()
// 将effectStack的栈顶元素标记为activeEffect
activeEffect = effectStack[effectStack.length - 1]
}
}
} as ReactiveEffect
effect.id = uid++ // 序号id加1
effect.allowRecurse = !!options.allowRecurse
effect._isEffect = true // 标识fn已经是effect副作用函数
effect.active = true
effect.raw = fn // 保存原值fn
effect.deps = [] // 依赖了哪些属性,哪些属性变化了需要执行当前effect
effect.options = options
return effect
}
3. trigger
当响应式变量值被修改时候,会通知依赖该变量的依赖着更新,在track阶段,依赖变量key的依赖着也就是副作用函数被依次保存在set结构中,当值被修改时候,则将set保存的副作用函数依次执行,触发vnode patch更新
export function trigger(
target: object,
type: TriggerOpTypes, // set add delete clear
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
const depsMap = targetMap.get(target)
if (!depsMap) {
// 如果没有收集过依赖,直接返回
return
}
// 存储依赖的effect
const effects = new Set<ReactiveEffect>() // [fn1,fn2,fn3]
// 添加存储
const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect || effect.allowRecurse) {
effects.add(effect)
}
})
}
}
// 在值被清空前,往相应的队列添加 target 所有的依赖
if (type === TriggerOpTypes.CLEAR) {
depsMap.forEach(add)
} else if (key === 'length' && isArray(target)) {
// 修改数组length
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= (newValue as number)) {
add(dep)
}
})
} else {
// SET | ADD | DELETE
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
}
}
// 执行effect
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()
}
}
// 遍历set结构保存的effect副作用函数
effects.forEach(run)
}