Vue3的响应式主要是通过proxy handlers来实现的,上一节有讲,Vue3的proxy handlers分为baseHandlers与collectionHandlers,这里主要讲baseHandlers。baseHandles又分为mutableHandlers与readonlyHandlers,顾名思义后者是只读的handlers
export const mutableHandlers: ProxyHandler = {
get: createGetter(false),
set,
deleteProperty,
has,
ownKeys
}
export const readonlyHandlers: ProxyHandler = {
get: createGetter(true),
set(target: any, key: string | symbol, value: any, receiver: any): boolean {
if (LOCKED) {
if (__DEV__) {
console.warn(
`Set operation on key "${String(key)}" failed: target is readonly.`,
target
)
}
return true
} else {
return set(target, key, value, receiver)
}
},
deleteProperty(target: any, key: string | symbol): boolean {
if (LOCKED) {
if (__DEV__) {
console.warn(
`Delete operation on key "${String(
key
)}" failed: target is readonly.`,
target
)
}
return true
} else {
return deleteProperty(target, key)
}
},
has,
ownKeys
}
两者的实现原理基本一致,这里主要讲mutableHandlers,实现响应式关键在于set和get方法,set主要用于发布(触发),get主要用于订阅(收集依赖)
先看订阅的实现
function createGetter(isReadonly: boolean) {
return function get(target: any, key: string | symbol, receiver: any) {
// console.log('get',target,key,receiver)
const res = Reflect.get(target, key, receiver)
if (isSymbol(key) && builtInSymbols.has(key)) {
return res
}
if (isRef(res)) {
return res.value
}
// console.log(isObject(res))
track(target, OperationTypes.GET, key)
return isObject(res)
? isReadonly
? // need to lazy access readonly and reactive here to avoid
// circular dependency
readonly(res)
: reactive(res)
: res
}
}
这里分别为每段代码做个解释
if (isSymbol(key) && builtInSymbols.has(key)) {
return res
}
这段代码判断key是否为内置的Symbol,如果是的话直接返回,不做依赖收集,例如:
const original = [1,2,3]
const observed = Vue.reactive(original)
Vue.effect(() => {
const foo = observed.foo
const testSymbol = observed[Symbol.iterator]
},{
onTrack:({effect,target,type,key}) => {
console.log(key) // foo
}
})
显然内置的Symbol并没有触发收集,至于effect副总用顾名思义,每当一个响应式的变量改变,会执行传入副作用内的匿名函数,
Vue3也是利用effect,每当值改变调用effect即组件生命周期update来重新执行页面,当然effect会在一开始执行一次以收集依赖,相对应的即组件生命周期的mounted。effect源码如下:
export function effect(
fn: () => T,
options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect {
if (isEffect(fn)) {
fn = fn.raw
}
const effect = createReactiveEffect(fn, options)
if (!options.lazy) {
effect()
}
return effect
}
此段代码主要2个功能,首先是判断传入的fn是否已经是响应式副作用函数的,如果是将fn置为fn.raw,例如:
const original = { foo: 1}
const observed = Vue.reactive(original)
const reactiveEffect = Vue.effect(() => {
console.log(observed.foo)
})
const reactiveEffect2 = Vue.effect(reactiveEffect)
// 打印2次1
接下来就是根据传入的fn与options创建响应式副作用,源码如下:
function createReactiveEffect(
fn: () => T,
options: ReactiveEffectOptions
): ReactiveEffect {
const effect = function reactiveEffect(...args: any[]): any {
return run(effect, fn, args)
} as ReactiveEffect
effect[effectSymbol] = true
effect.active = true
effect.raw = fn
effect.scheduler = options.scheduler
effect.onTrack = options.onTrack
effect.onTrigger = options.onTrigger
effect.onStop = options.onStop
effect.computed = options.computed
effect.deps = []
return effect
}
主要作用是创建一个effect函数,并将其effect标识符置为true,active状态置为true,deps依赖置为空数组,将传入的fn赋值给raw属性并将传入的option参数赋值给effect,effect执行函数讲run方法包装了一层返回,run函数主要源码如下:
function run(effect: ReactiveEffect, fn: Function, args: any[]): any {
if (!effect.active) {
return fn(...args)
}
if (activeReactiveEffectStack.indexOf(effect) === -1) {
cleanup(effect)
try {
activeReactiveEffectStack.push(effect)
return fn(...args)
} finally {
activeReactiveEffectStack.pop()
}
}
}
首先判定effect是否是激活状态,如果非激活状态不进行依赖收集,直接执行函数内容,响应式副作用失去了响应式的功能。例如:
const original = { foo: 1}
const observed = Vue.reactive(original)
const reactiveEffect = Vue.effect(() => {
console.log(observed.foo) // 1,1
})
Vue.stop(reactiveEffect)
reactiveEffect()
observed.foo = 2
注:Vue.stop函数需要自己引用出来,默认是不对外提供的,stop函数对应的源码是
export function stop(effect: ReactiveEffect) {
if (effect.active) {
cleanup(effect)
if (effect.onStop) {
effect.onStop()
}
effect.active = false
}
}
主要是3个功能,第一个是清空effect的相关dep,第二个是调用传入effect的onStop函数,第三是讲effect激活状态置为false,
cleanup的源码如下
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
}
}
effect的deps数组中每个对象指针指向与全局targetMap对应targetMap的对应dep所指针指向是相同的,当effect被删除后,即之后trigger时,不会再调用被删除的副作用。
接着讲run函数,activeReactiveEffectStack为当前运行effect的栈,因为effect内可能嵌套另外一个effect,类似于函数调用,每当调用一个effect时,将effect入栈,然后运行effect函数,effect内响应式对象出发proxy的get,然后将其与effect绑定,之后每当响应式对象发生变化时,调用与其绑定的effect函数。执行完effect之后即依赖收集完成,activeReactiveEffectStack pop出栈。
注意到还有2行代码,第一个是:
if (activeReactiveEffectStack.indexOf(effect) === -1) {
// xx
}
这行代码即判断如果当前activeReactiveEffectStack中已经存在相同effect时候,不运行effect函数,主要作用是防止用户effect内嵌套相同effect,例如:
var observed = Vue.reactive({foo:10})
let effect = Vue.effect(() => {
console.log(observed.foo) // 显示一次10
effect()
},{
lazy:true
})
effect()
另外一行代码为:
cleanup(effect)
如果不删除之前的effect,那么则不会再次进行依赖收集,有可能会导致执行不必要的effect执行,不信将这行代码删除,运行如下代码:
var observed = Vue.reactive({foo:10})
var observed2 = Vue.reactive({num:1})
Vue.effect(() => {
if(observed2.num === 1) {
console.log('触发effect') //触发了4次
}else {
let data = observed.foo
}
})
observed2.num = 2
observed2.num = 1
observed.foo = 50
observed.foo = 60
很明显看出,当observed2.num为1时,observed.foo的值改变不需要重新执行effect。