我们知道Vue3通过proxy来实现响应式,总的来说就是getter中收集依赖,setter中触发依赖。提供的reactive可以将数据变成响应式,我们来看下reactive的实现方式。
function reactive (target) {
// 如果尝试把一个 readonly proxy 变成响应式,直接返回这个 readonly proxy
if (target && target.__v_isReadonly) {
return target
}
return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers)
}
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers) {
if (!isObject(target)) {
// 目标必须是对象或数组类型
if ((process.env.NODE_ENV !== 'production')) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
if (target.__v_raw && !(isReadonly && target.__v_isReactive)) {
// target 已经是 Proxy 对象,直接返回
return target
}
// 利用 Proxy 创建响应式
const observed = new Proxy(target, collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers)
// 给原始数据打个标识,说明它已经变成响应式,并且有对应的 Proxy 了
def(target, isReadonly ? "__v_readonly" /* readonly */ : "__v_reactive" /* reactive */, observed)
return observed
}
可以看到,reactive内部调用的还是createReactiveObject函数。
createReactiveObject内部首先判断target是不是对象,因为reactive作用的对象是Object。- 如果对一个已经是响应式的对象再次执行 reactive,还应该返回这个响应式对象。
- 重点:
通过 Proxy 创建响应式。
接下来就看Proxy中的处理器,里面会涉及到getter、setter。接下来看看如何在getter中收集依赖,setter中触发依赖。
依赖收集:get 函数
以下代码只涉及到关键部分,细节部分省略。
function createGetter(isReadonly = false) {
return function get(target, key, receiver) {
......
// 求值
const res = Reflect.get(target, key, receiver)
// 依赖收集
!isReadonly && track(target, "get" /* GET */, key)
return isObject(res)
? isReadonly
?
readonly(res)
// 如果 res 是个对象或者数组类型,则递归执行 reactive 函数把 res 变成响应式
: reactive(res)
: res
}
}
可以看到我们是 先通过 Reflect.get 求值,然后再执行 track 函数收集依赖.
track函数
track函数中我们需要了解一个思路,响应式作用的目标target是对象,对象有好多属性,通过map存储;每个属性可能在不同地方使用,因此对应不同的副作用,存储到Set中防止重复。整个的关系就是:
// 是否应该收集依赖
let shouldTrack = true
// 当前激活的 effect
let activeEffect
// 原始数据对象 map
const targetMap = new WeakMap()
function track(target, type, key) {
if (!shouldTrack || activeEffect === undefined) {
return
}
let depsMap = targetMap.get(target)
if (!depsMap) {
// 每个 target 对应一个 depsMap
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
// 每个 key 对应一个 dep 集合
depsMap.set(key, (dep = new Set()))
}
if (!dep.has(activeEffect)) {
// 收集当前激活的 effect 作为依赖
dep.add(activeEffect)
// 当前激活的 effect 收集 dep 集合作为依赖
activeEffect.deps.push(dep)
}
}
全局有很多响应式对象,我们将这些响应式对象也放到一个Map中存储,这里我们使用WeakMap存储,使用weakMap目的是能将 不再使用的数据进行正确的垃圾回收。
每次 track ,就是把当前激活的副作用函数 activeEffect 作为依赖,然后收集到 target 相关的 depsMap 对应 key 下的依赖集合 dep 中。
trigger函数
// 原始数据对象 map
const targetMap = new WeakMap()
function trigger(target, type, key, newValue) {
// 通过 targetMap 拿到 target 对应的依赖集合
const depsMap = targetMap.get(target)
if (!depsMap) {
// 没有依赖,直接返回
return
}
// 创建运行的 effects 集合
const effects = new Set()
// 添加 effects 的函数
const add = (effectsToAdd) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
effects.add(effect)
})
}
}
// SET | ADD | DELETE 操作之一,添加对应的 effects
if (key !== void 0) {
add(depsMap.get(key))
}
const run = (effect) => {
// 调度执行
if (effect.options.scheduler) {
effect.options.scheduler(effect)
}
else {
// 直接运行
effect()
}
}
// 遍历执行 effects
effects.forEach(run)
}
trigger函数与track函数对应的,主要做了四件事情:
- 通过 targetMap 拿到 target 对应的依赖集合 depsMap;
- 创建运行的 effects 集合;
- 根据 key 从 depsMap 中找到对应的 effects 添加到 effects 集合;
- 遍历 effects 执行相关的副作用函数。
所以每次 trigger 函数就是根据 target 和 key ,从 targetMap 中找到相关的所有副作用函数遍历执行一遍。