引言
最近在使用Vue3,使用一段时间后,就想研究下源码。参考网上的资料,如果是刚上手,可以从Reactivity
看起。因为Reactivity
是整个Vue3
中跟外部没有任何耦合的一个模块。而Reactivity
则是大名鼎鼎的响应式功能。
注: 源码地址:github.com/vuejs/core
基础函数
注:在packages\reactivity\src\index.ts
中我们可以找到常用函数的代码路径。
ref
由于项目中常使用的ref
,所以我先去找到ref
函数。
// packages\reactivity\src\ref.ts
export function ref(value?: unknown) {
return createRef(value, false)
}
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
可以看到在传入参数时,ref
会返回一个RefImpl
实例对象。
class RefImpl<T> {
private _value: T
private _rawValue: T
public dep?: Dep = undefined
public readonly __v_isRef = true
constructor(value: T, public readonly __v_isShallow: boolean) {
this._rawValue = __v_isShallow ? value : toRaw(value)
this._value = __v_isShallow ? value : toReactive(value)
}
get value() {
trackRefValue(this)
return this._value
}
set value(newVal) {
newVal = this.__v_isShallow ? newVal : toRaw(newVal)
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = this.__v_isShallow ? newVal : toReactive(newVal)
triggerRefValue(this, newVal)
}
}
}
// packages\reactivity\src\reactive.ts
export const toReactive = <T extends unknown>(value: T): T =>
isObject(value) ? reactive(value) : value
我们可以看到,RefImpl
实例对象包含一个value属性,其值为_value
(即我们平时写代码中的xx.value
)。在value属性的get
、set
方法中,触发了trackRefValue
、triggerRefValue
方法,用于实现相关响应式逻辑。
而通过toReactive
方法,其实可以看到针对对象的情况,ref
返回的值即为reactive
方法返回的值;而非对象,则返回原数值。
注:此次先暂时不研究_rawValue
、trackRefValue
等相关的具体逻辑,避免影响主体思路,下同。
reactive
上面说到ref
方法中调用了reactive
方法,那么我们接下来看一下reactive
方法的逻辑。
// packages\reactivity\src\reactive.ts
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (isReadonly(target)) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
}
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
...
// target already has corresponding Proxy
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// only a whitelist of value types can be observed.
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}
可以看到,createReactiveObject
中是生成了一个Proxy
对象(不太了解Proxy
的,可以学习一下这里),handles
参数是mutableHandlers
或者mutableCollectionHandlers
。我们先来研究shallowReactiveHandlers
。
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys
}
const get = /*#__PURE__*/ createGetter()
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
...
const targetIsArray = isArray(target)
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
const res = Reflect.get(target, key, receiver)
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res
}
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
...
if (isRef(res)) {
// ref unwrapping - does not apply for Array + integer key.
const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
return shouldUnwrap ? res.value : res
}
if (isObject(res)) {
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
可以看到,在get
方法中,若返回值为对象,则会返回一个reactive(res)
,这样该返回对象的属性也能实现响应式;并且,在获取属性值时,也会触发track(target, TrackOpTypes.GET, key)
方法,用于该属性响应式相关逻辑。
const set = /*#__PURE__*/ createSetter()
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
let oldValue = (target as any)[key]
if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
return false
}
if (!shallow && !isReadonly(value)) {
if (!isShallow(value)) {
value = toRaw(value)
oldValue = toRaw(oldValue)
}
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
}
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
const result = Reflect.set(target, key, value, receiver)
// don't trigger if target is something up in the prototype chain of original
if (target === toRaw(receiver)) {
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
}
可以看到在set
方法中,在更新属性值的同时,若为添加属性则触发trigger(target, TriggerOpTypes.ADD, key, value)
,反之则触发trigger(target, TriggerOpTypes.SET, key, value, oldValue)
,用于响应式相关逻辑。
computed
// packages\reactivity\src\computed.ts
export function computed<T>(
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
debugOptions?: DebuggerOptions,
isSSR = false
) {
let getter: ComputedGetter<T>
let setter: ComputedSetter<T>
const onlyGetter = isFunction(getterOrOptions)
if (onlyGetter) {
getter = getterOrOptions
setter = __DEV__
? () => {
console.warn('Write operation failed: computed value is readonly')
}
: NOOP
} else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}
const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR)
...
return cRef as any
}
我们可以看到,在解析出getter
和setter
函数后,computed
实际返回的是一个ComputedRefImpl
实例对象。
export class ComputedRefImpl<T> {
public dep?: Dep = undefined
private _value!: T
public readonly effect: ReactiveEffect<T>
public readonly __v_isRef = true
public readonly [ReactiveFlags.IS_READONLY]: boolean
public _dirty = true
public _cacheable: boolean
constructor(
getter: ComputedGetter<T>,
private readonly _setter: ComputedSetter<T>,
isReadonly: boolean,
isSSR: boolean
) {
this.effect = new ReactiveEffect(getter, () => {
if (!this._dirty) {
this._dirty = true
triggerRefValue(this)
}
})
this.effect.computed = this
this.effect.active = this._cacheable = !isSSR
this[ReactiveFlags.IS_READONLY] = isReadonly
}
get value() {
// the computed ref may get wrapped by other proxies e.g. readonly() #3376
const self = toRaw(this)
trackRefValue(self)
if (self._dirty || !self._cacheable) {
self._dirty = false
self._value = self.effect.run()!
}
return self._value
}
set value(newValue: T) {
this._setter(newValue)
}
}
这里我们可以看到在初始ComputedRefImpl
实例对象时,通过ReactiveEffect
创建了一个effect
对象,并且在取值的时候调用了trackRefValue(self)
来执行响应式相关逻辑。
effect
Vue 通过一个副作用 (effect) 来跟踪当前正在运行的函数。副作用是一个函数的包裹器,在函数被调用之前就启动跟踪。Vue 知道哪个副作用在何时运行,并能在需要时再次执行它。
从Vue3的官方文档,我们可以看出数据的响应式变化和effect
有关,这里我们深入研究一下。
ref
还是先从ref
开始,我们从上面知道,在获取ref的值时触发了trackRefValue
,在赋值ref时触发了triggerRefValue
。
// packages\reactivity\src\ref.ts
export function trackRefValue(ref: RefBase<any>) {
if (shouldTrack && activeEffect) {
ref = toRaw(ref)
if (__DEV__) {
...
} else {
trackEffects(ref.dep || (ref.dep = createDep()))
}
}
}
// packages\reactivity\src\dep.ts
export const createDep = (effects?: ReactiveEffect[]): Dep => {
const dep = new Set<ReactiveEffect>(effects) as Dep
dep.w = 0
dep.n = 0
return dep
}
// packages\reactivity\src\effect.ts
export let activeEffect: ReactiveEffect | undefined
export function trackEffects(
dep: Dep,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
let shouldTrack = false
...
if (shouldTrack) {
dep.add(activeEffect!)
activeEffect!.deps.push(dep)
...
}
}
可以看到当调用属性的get
逻辑时,更新了activeEffect
对象(其一定存在,后面会讲解更新的作用)。那么,我们想象到在渲染模板或者计算computed
等需要ref
数据时,都会将相关逻辑(即ReactiveEffect
)放置到dep
中。
// packages\reactivity\src\ref.ts
export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
ref = toRaw(ref)
if (ref.dep) {
if (__DEV__) {
...
} else {
triggerEffects(ref.dep)
}
}
}
// packages\reactivity\src\effect.ts
export function triggerEffects(
dep: Dep | ReactiveEffect[],
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
// spread into array for stabilization
for (const effect of isArray(dep) ? dep : [...dep]) {
if (effect !== activeEffect || effect.allowRecurse) {
...
if (effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
}
}
}
export class ReactiveEffect<T = any> {
active = true
deps: Dep[] = []
...
constructor(
public fn: () => T,
public scheduler: EffectScheduler | null = null,
scope?: EffectScope
) {
recordEffectScope(this, scope)
}
run() {
if (!this.active) {
return this.fn()
}
...
try {
...
return this.fn()
} finally {
...
}
}
...
}
这里我们能看出,当更新ref
数据时,会依次触发dep
中记录的ReactiveEffect
的scheduler
函数或者run
函数,来完成相应的响应式逻辑。
reactive
reactive
中get
调用的track
函数,而track
本质也是调用的trackEffects
用于更新dep
数据。
// packages\reactivity\src\effect.ts
export function track(target: object, type: TrackOpTypes, key: unknown) {
if (shouldTrack && activeEffect) {
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = createDep()))
}
const eventInfo = __DEV__
? { effect: activeEffect, target, type, key }
: undefined
trackEffects(dep, eventInfo)
}
}
reactive
中set
调用的trigger
函数,其中调用的trackEffects
用于实现响应式数据的更新。
// packages\reactivity\src\effect.ts
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
const depsMap = targetMap.get(target)
if (!depsMap) {
// never been tracked
return
}
let deps: (Dep | undefined)[] = []
...
const eventInfo = __DEV__
? { target, type, key, newValue, oldValue, oldTarget }
: undefined
if (deps.length === 1) {
if (deps[0]) {
if (__DEV__) {
triggerEffects(deps[0], eventInfo)
} else {
triggerEffects(deps[0])
}
}
} else {
const effects: ReactiveEffect[] = []
for (const dep of deps) {
if (dep) {
effects.push(...dep)
}
}
if (__DEV__) {
triggerEffects(createDep(effects), eventInfo)
} else {
triggerEffects(createDep(effects))
}
}
}
computed
computed
中也是触发的trackRefValue
等逻辑,这里就不再过多叙述。这里需要注意的是在获取computed
的值时,存在effect.run()
去重新计算值的情况。