依赖收集
| 版本 | 依赖收集 | 派发更新 |
|---|---|---|
| Vue2 | dep.depend | dep.notify |
| Vue3 | track | trigger |
响应式
| 版本 | 响应式 |
|---|---|
| Vue2 | data中设置、$set() |
| Vue3 | reactive,ref、shallowReactive、shallowReadonly |
Vue3中使用高阶函数:函数柯里化
Vue3响应式源码
Vue3 中定义的几个用来标记目标对象 target 的类型的flag。
// packages/reactivity/src/reactive.ts -16行
export const enum ReactiveFlags {
SKIP = '__v_skip',
IS_REACTIVE = '__v_isReactive',
IS_READONLY = '__v_isReadonly',
RAW = '__v_raw'
}
export interface Target {
[ReactiveFlags.SKIP]?: boolean // 不做响应式处理的数据
[ReactiveFlags.IS_REACTIVE]?: boolean // target 是否是响应式
[ReactiveFlags.IS_READONLY]?: boolean // target 是否是只读的
[ReactiveFlags.RAW]?: any // 表示 proxy 对应的源数据,target 已经是 proxy 对象时会有该属性
}
reactive
// packages/reactivity/src/reactive.ts -87行
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
// 如果 target 是只读类型的对象就直接返回
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
return createReactiveObject(
target, // 需要创建响应式的目标对象 data
false, // 不是只读类型
mutableHandlers,
mutableCollectionHandlers,
reactiveMap // const reactiveMap = new WeakMap<Target, any>()
)
}
createReactiveObject
// packages/reactivity/src/reactive.ts -181行
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
// typeof 不是 object 类型的,直接返回
if (!isObject(target)) {
if (__DEV__) console.warn(`value cannot be made reactive: ${String(target)}`)
return target
}
// 已经是响应式的就直接返回
if ( target[ReactiveFlags.RAW] && !(isReadonly && target[ReactiveFlags.IS_REACTIVE])) {
return target
}
// 如果已经存在 map 中了,就直接返回
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 不做响应式的,直接返回
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// 把 target 转为 proxy
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
// 添加到 map 里
proxyMap.set(target, proxy)
return proxy
}
大概了解了这个方法里要做的事,可以看出一个很重要的东西,就是proxy的第二个参数 handlers。
baseHandlers 处理数组,对象;collectionHandlers处理 Map、Set、WeakMap、WeakSet。
在 basehandlers 中包含了四种 handler:
- mutableHandlers 可变处理
- readonlyHandlers 只读处理
- shallowReactiveHandlers 浅观察处理(只观察目标对象的第一层属性)
- shallowReadonlyHandlers 浅观察 && 只读处理 其中 readonlyHandlers、shallowReactiveHandlers、shallowReadonlyHandlers 都是 mutableHandlers 的变形版本
mutableHandlers 定义是这样的:
const get = /*#__PURE__*/ createGetter()
const set = /*#__PURE__*/ createSetter()
export const mutableHandlers: ProxyHandler<object> = {
get, // 获取属性
set, // 修改属性
deleteProperty, // 删除属性
has, // 是否拥有某个属性
ownKeys // 收集 key,包括 symbol 类型或者不可枚举的 key
}
get、has、ownKeys 会触发依赖收集 track();
set、deleteProperty 会触发更新 trigger()。
使用weakMap的优点:
- key是对象
- 可以自动释放
createGetter()
// packages/reactivity/src/baseHandlers.ts -80行
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
// 访问对应标记位
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
} else if (
// receiver 指向调用者,这里判断是为了保证触发拦截 handle 的是 proxy 本身而不是 proxy 的继承者
// 触发拦的两种方式:一是访问 proxy 对象本身的属性,二是访问对象原型链上有 proxy 对象的对象的属性,因为查询会沿着原型链向下找
key === ReactiveFlags.RAW &&
receiver ===
(isReadonly
? shallow ? shallowReadonlyMap : readonlyMap
: shallow ? shallowReactiveMap : reactiveMap
).get(target)
) {
// 返回 target 本身,也就是响应式对象的原始值
return target
}
// 是否是数组
const targetIsArray = isArray(target)
// 不是只读类型 && 是数组 && 触发的是 arrayInstrumentations 工具集里的方法
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
// 通过 proxy 调用,arrayInstrumentations[key]的this一定指向 proxy
return Reflect.get(arrayInstrumentations, key, receiver)
}
// proxy 预返回值
const res = Reflect.get(target, key, receiver)
// key 是 symbol 或访问的是__proto__属性不做依赖收集和递归响应式处理,直接返回结果
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res
}
// 不是只读类型的 target 就收集依赖。因为只读类型不会变化,无法触发 setter,也就会触发更新
if (!isReadonly) {
// 收集依赖,存储到对应的全局仓库中
track(target, TrackOpTypes.GET, key)
}
// 浅比较,不做递归转化,就是说对象有属性值还是对象的话不递归调用 reactive()
if (shallow) {
return res
}
// 访问的属性已经是 ref 对象
if (isRef(res)) {
// 返回 ref.value,数组除外
const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
return shouldUnwrap ? res.value : res
}
// 由于 proxy 只能代理一层,如果子元素是对象,需要递归继续代理
// 懒代理,Vue3的性能优化点(Vue2时,直接递归操作,Vue3的时候,不使用,就不会执行)
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
track() 依赖收集放到后面,和派发更新一起
createSetter()
// packages/reactivity/src/baseHandlers.ts -141行
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
let oldValue = (target as any)[key]
if (!shallow) {
// 拿新值和老值的原始值,因为新传入的值可能是响应式数据,如果直接和 target 上原始值比较是没有意义的
value = toRaw(value)
oldValue = toRaw(oldValue)
// 不是数组 && 老值是 ref && 新值不是 ref,更新 ref.value 为新值
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
}
} else {
// in shallow mode, objects are set as-is regardless of reactive or not
}
// 获取 key 值
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
// 赋值,相当于 target[key] = value
const result = Reflect.set(target, key, value, receiver)
// receiver 是 proxy 实例才派发更新,防止通过原型链触发拦截器触发更新
if (target === toRaw(receiver)) {
if (!hadKey) {
// 如果 target 没有 key,表示新增
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
// 如果新旧值不相等
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
}
为什么用 Reflect.get() 和 Reflect.set(),而不是直接用 target[key]?
Reflect.get(target, prop, receiver)中的参数receiver:如果target对象中指定了getter,receiver则为getter调用时的this值。handler.get(target, prop, receiver)中的参数receiver:Proxy或者继承Proxy的对象。 详细解答
ReactiveEffect()
// 临时存储响应式函数
const effectStack: ReactiveEffect[] = []
// 依赖收集栈
const trackStack: boolean[] = []
// 最大嵌套深度
const maxMarkerBits = 30
这里主要做的就是在依赖收集前用栈数据结构 effectStrack 来做 effect 的执行调试,保证当前 effect 的优先级最高,并及时清除己收集依赖的内存。
// packages/reactivity/src/effect.ts -53行
export class ReactiveEffect<T = any> {
active = true
deps: Dep[] = []
// can be attached after creation
computed?: boolean
allowRecurse?: 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 | null
) {
recordEffectScope(this, scope)
}
run() {
if (!this.active) {
return this.fn()
}
// 如果栈中没有当前的 effect
if (!effectStack.includes(this)) {
try {
// 先把当前 effect 设置为全局全局激活的 effect,在 getter 中会收集 activeEffect 持有的 effect, 然后入栈
effectStack.push((activeEffect = this))
// 恢复依赖收集,因为在setup 函数自行期间,会暂停依赖收集
enableTracking()
// 记录递归深度位数
trackOpBit = 1 << ++effectTrackDepth
// 如果 effect 嵌套层数没有超过 30 层,一般超不了
if (effectTrackDepth <= maxMarkerBits) {
// 给依赖打标记,就是遍历 _effect 实例中的 deps 属性,给每个 dep 的 w 属性标记为 trackOpBit 的值
initDepMarkers(this)
} else {
// 超过就 清除当前 effect 相关依赖 通常情况下不会
cleanupEffect(this)
}
return this.fn()
} finally {
if (effectTrackDepth <= maxMarkerBits) {
// 完成依赖标记
finalizeDepMarkers(this)
}
// 恢复到上一级
trackOpBit = 1 << --effectTrackDepth
// 重置依赖收集状态
resetTracking()
// 出栈
effectStack.pop()
// 获取栈长度
const n = effectStack.length
// 将当前 activeEffect 指向栈最后一个 effect
activeEffect = n > 0 ? effectStack[n - 1] : undefined
}
}
}
stop() {
if (this.active) {
cleanupEffect(this)
if (this.onStop) {
this.onStop()
}
this.active = false
}
}
}
该函数调用在runtime-core/src/renderer.ts,初始化ReactiveEffect对象,调用run函数。
需要注意的是标记完成后就会执行 fn() 函数,这个 fn 函数就是副作用函数封闭的函数,如果是在组件渲染,就是 fn 就是组件渲染函数,执行的时候就会就会访问数据,就会触发 target[key] 的 getter,然后触发 track 进行依赖收集,这也就是 Vue3 的依赖收集过程。
track()
track 就是依赖收集器,负责把依赖收集起来统一放到一个依赖管理中心
const targetMap = new WeakMap<any, KeyToDepMap>()
export function isTracking() {
return shouldTrack && activeEffect !== undefined
}
targetMap 为依赖管理中心,用于存储响应式函数、目标对象、键之间的映射关系。结构类似下面代码:
// targetMap(weakmap)={
// target1(map):{
// key1(dep):[effect1,effect2]
// key2(dep):[effect1,effect2]
// }
// }
// 给每个 target 创建一个 map,每个 key 对应着一个 dep
// 用 dep 来收集依赖函数,监听 key 值变化,触发 dep 中的依赖函数
track()
// packages/reactivity/src/effect.ts -188行
export function track(target: object, type: TrackOpTypes, key: unknown) {
// 如果当前没有激活 effect,就不用收集,首次创建时未执行get操作,故为false
if (!isTracking()) {
return
}
// 从依赖管理中心里获取 target
let depsMap = targetMap.get(target)
if (!depsMap) {
// 如果没有就创建一个
targetMap.set(target, (depsMap = new Map()))
}
// 获取 key 对应的 dep 集合
let dep = depsMap.get(key)
if (!dep) {
// 没有就创建
depsMap.set(key, (dep = createDep()))
}
// 开发环境和非开发环境
const eventInfo = __DEV__
? { effect: activeEffect, target, type, key }
: undefined
trackEffects(dep, eventInfo)
}
trackEffects()
这里把当前激活的effect收集进对应的 effect 集合,也就是 dep
dep.n:n 是 newTracked 的缩写,表示是否是最新收集的(是否当前层)
dep.w:w 是 wasTracked 的缩写,表示是否已经被收集,避免重复收集
// packages/reactivity/src/effect.ts -212行
export function trackEffects(
dep: Dep,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
let shouldTrack = false
// 如果 effect 嵌套层数没有超过30层
if (effectTrackDepth <= maxMarkerBits) {
if (!newTracked(dep)) {
// 标记新依赖
dep.n |= trackOpBit // set newly tracked
// 已经被收集的依赖不需要重复收集
shouldTrack = !wasTracked(dep)
}
} else {
// 超过了 就切换清除依赖模式
// Full cleanup mode.
shouldTrack = !dep.has(activeEffect!)
}
if (shouldTrack) {
// 收集当前激活的 effect 作为依赖
dep.add(activeEffect!)
// 当前激活的 effect 收集 dep 集合
activeEffect!.deps.push(dep)
// 开发环境下触发 onTrack 事件
if (__DEV__ && activeEffect!.onTrack) {
activeEffect!.onTrack(
Object.assign(
{
effect: activeEffect!
},
debuggerEventExtraInfo
)
)
}
}
}
以上为依赖收集的过程。
trigger()
trigger 是 track 收集的依赖对应的触发器,也就是负责根据映射关系,获取响应式函数,再派发通知 triggerEffects 进行更新。
未完待续~