前言
对响应式还不理解的同学建议去看看我这篇文章响应式原型,下面我会从Vue3源码来分析响应式的核心功能。分析的内容有下面内容:
- reactive 的实现
- track 依赖收集
- trigger 触发依赖
- effect.scheduler
- effect.stop
- readonly 的实现
- isReactive
- isReadonly
- 嵌套 reactive
- 嵌套 readonly
- isRef
- unref
- computed
effect篇
测试一:基本功能
js
it('should run the passed function once (wrapped by a effect)', () => {
const fnSpy = jest.fn(() => {})
effect(fnSpy)
expect(fnSpy).toHaveBeenCalledTimes(1)
})
- 使用jest.fn模拟函数,便于监听调用情况,此时我们可以看到,effect函数进行包裹的时候就会进行一次fnSpy的执行,我们看看effect函数做了什么。
js
/packages/reactivity/src/effect.ts
export function effect<T = any>(
fn: () => T,
options?: ReactiveEffectOptions
): ReactiveEffectRunner {
if ((fn as ReactiveEffectRunner).effect) {
fn = (fn as ReactiveEffectRunner).effect.fn
}
// 关注这里的 new ReactiveEffect(fn)
const _effect = new ReactiveEffect(fn)
if (options) {
extend(_effect, options)
if (options.scope) {
recordEffectScope(_effect, options.scope)
}
}
if (!options || !options.lazy) {
_effect.run()
}
const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
runner.effect = _effect
return runner
}
- 我们先关注 new ReactiveEffect(fn)
js
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() {
···
return this.fn()
···
}
···
}
这里很容易就可以看明白,构造函数接受了fn参数,然后定义了run 方法,当执行run方法的时候,则执行this.fn(),也就是在前面我们可以看到执行了_effect.run()。
测试二:基本功能
js
it('should observe basic properties', () => {
let dummy
const counter = reactive({ num: 0 })
effect(() => (dummy = counter.num))
expect(dummy).toBe(0)
counter.num = 7
expect(dummy).toBe(7)
})
effect传入一个函数,在这个函数有响应式数据,当响应式数据发生变化的时候,传入的函数会再次执行
再次回到effect函数中,每次执行effect都会new ReactiveEffect,得到_effect
实例,然后执行_effect.run
js
const effectStack: ReactiveEffect[] = []
let activeEffect: ReactiveEffect | undefined
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()
}
if (!effectStack.includes(this)) {
try {
effectStack.push((activeEffect = this))
enableTracking()
trackOpBit = 1 << ++effectTrackDepth
if (effectTrackDepth <= maxMarkerBits) {
initDepMarkers(this)
} else {
cleanupEffect(this)
}
return this.fn()
} finally {
if (effectTrackDepth <= maxMarkerBits) {
finalizeDepMarkers(this)
}
trackOpBit = 1 << --effectTrackDepth
resetTracking()
effectStack.pop()
const n = effectStack.length
activeEffect = n > 0 ? effectStack[n - 1] : undefined
}
}
}
}
提前定义了两个变量 effectStack、activeEffect。当执行run的时候,会判断effectStack中是否已经存过activeEffect,如果没有存过则执行effectStack.push((activeEffect = this))
这里做了两步操作,一个是将this赋值给activeEffect, 然后将activeEffect保存在effectStack。然后执行了this.fn。当执行了this.fn就出触发依赖收集,则会执行track
。然后把activeEffect收集到dep
中,完成依赖收集,那么后续的修改触发set,便会执行dep里的activeEffect(),如下
js
// 依赖收集
export function track(target: object, type: TrackOpTypes, key: unknown) {
if (!isTracking()) {
return
}
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()))
}
trackEffects(dep, eventInfo)
}
export function trackEffects(
dep: Dep,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
···
dep.add(activeEffect!)
···
}
当响应式数据发生了变化,则会触发set。然后会触发trigger去触发依赖更新
js
export function triggerEffects(
dep: Dep | ReactiveEffect[],
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
// spread into array for stabilization
for (const effect of isArray(dep) ? dep : [...dep]) {
···
effect.run()
···
}
}
因为执行了run ,所以之前传入的fn会再次执行,所以测试可以通过
测试三:实现返回值
js
it('should return a new reactive version of the function', () => {
function greet() {
return 'Hello World'
}
const effect1 = effect(greet)
const effect2 = effect(greet)
expect(typeof effect1).toBe('function')
expect(typeof effect2).toBe('function')
expect(effect1).not.toBe(greet)
expect(effect1).not.toBe(effect2)
})
测试用例中effect会返回一个函数。看着这个函数是一个什么样的函数。
js
export function effect<T = any>(
fn: () => T,
options?: ReactiveEffectOptions
): ReactiveEffectRunner {
if ((fn as ReactiveEffectRunner).effect) {
fn = (fn as ReactiveEffectRunner).effect.fn
}
const _effect = new ReactiveEffect(fn)
···
const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
runner.effect = _effect
return runner
}
其实返回的就是_effect.run,每一次进行effect包裹就会创建一个实例对象,有一个小伏笔runner.effect = _effect
可以考虑到后续使用effect上的方法,我们看下面的用例:
测试四:实现effect.scheduler
js
it('scheduler', () => {
let dummy
let run: any
const scheduler = jest.fn(() => {
run = runner
})
const obj = reactive({ foo: 1 })
const runner = effect(
() => {
dummy = obj.foo
},
{ scheduler }
)
// 首次会执行一次_fn()
expect(scheduler).not.toHaveBeenCalled()
expect(dummy).toBe(1)
// should be called on first trigger
// 第二次会会通过scheduler来控制执行时机
obj.foo++
expect(scheduler).toHaveBeenCalledTimes(1)
// should not run yet
expect(dummy).toBe(1)
// manually run
run()
// should have run
expect(dummy).toBe(2)
})
首先模拟一个函数scheduler, 然后用对象包裹放到effect的第二个参数中。进行effect的包裹,则会执行一次_fn(),当响应式数据发生改变的时候,则执行了scheduler。
js
export const extend = Object.assign
export function effect<T = any>(
fn: () => T,
options?: ReactiveEffectOptions
): ReactiveEffectRunner {
if ((fn as ReactiveEffectRunner).effect) {
fn = (fn as ReactiveEffectRunner).effect.fn
}
const _effect = new ReactiveEffect(fn)
if (options) {
extend(_effect, options)
if (options.scope) recordEffectScope(_effect, options.scope)
}
if (!options || !options.lazy) {
_effect.run()
}
const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
runner.effect = _effect
return runner
}
effect的第二个参数options, 代码中做了判断,如果以后这个options参数,则执行将options和_effect合并到_effect上,此时_effect就有了scheduler属性。当响应式数据发生变化的时候,执行trigger,接着执行triggerEffect时。
js
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 (__DEV__ && effect.onTrigger) {
effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
}
if (effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
}
}
}
如果scheduler存在的时候则执行scheduler,就不会执行run了。
测试四:实现effect.stop
js
it('stop', () => {
let dummy
const obj = reactive({ prop: 1 })
const runner = effect(() => {
dummy = obj.prop
})
obj.prop = 2
expect(dummy).toBe(2)
stop(runner)
obj.prop = 3
expect(dummy).toBe(2)
// stopped effect should still be manually callable
runner()
expect(dummy).toBe(3)
})
这段测试代码中将effect返回的函数用stop包裹了一下,然后当响应式数据发生了变化,就不会在触发依赖更新了。
猜测:当执行stop的时候,将dep的对应的依赖删掉?
js
export function stop(runner: ReactiveEffectRunner) {
runner.effect.stop()
}
stop的参数是runner,也就是测试中effect执行返回的runner。
js
export function effect<T = any>(
fn: () => T,
options?: ReactiveEffectOptions
): ReactiveEffectRunner {
const _effect = new ReactiveEffect(fn)
const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
runner.effect = _effect
return runner
}
上面说到runner其实就是_effect.run
,然后有吧new ReactiveEffect
的实例_effect
挂到了runner.effect上面。stop中就是执行的这个实例上面的stop。
js
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() {
···
}
stop() {
if (this.active) {
cleanupEffect(this)
if (this.onStop) {
this.onStop()
}
this.active = false
}
}
}
function cleanupEffect(effect: ReactiveEffect) {
const { deps } = effect
if (deps.length) {
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect)
}
deps.length = 0
}
}
在执行stop的时候,this.active判断是否已经清空过了(性能优化)。然后执行了cleanupEffect(this),可以想到这一步就是清除依赖的。在cleanupEffect中首先是从实例中拿到deps。
疑问:那这个deps是什么时候存的呢?
回忆一下收集依赖的时候:
js
export function trackEffects(
dep: Dep,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
let shouldTrack = false
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) {
dep.add(activeEffect!)
activeEffect!.deps.push(dep)
}
}
当dep收集到当前的activeEffect,反向activeEffect中的deps也收集了dep。
理解一下:为什么要反向收集?因为要知道当前的副作用被dep收集了,后续清除依赖的时候,可以一并把这些dep中的activeEffect全部清掉。
上面说到拿到deps,然后遍历删除对应的activeEffect就可以了。所以此时当响应式数据再次发生变化的时候就找不到对应的依赖了。
测试五:实现onStop
js
import {
reactive,
effect,
stop,
toRaw,
TrackOpTypes,
TriggerOpTypes,
DebuggerEvent,
markRaw,
shallowReactive,
readonly,
ReactiveEffectRunner
} from '../src/index'
it('events: onStop', () => {
const onStop = jest.fn()
const runner = effect(() => {}, {
onStop
})
stop(runner)
expect(onStop).toHaveBeenCalled()
})
测试中,模拟了函数onStop,然后将这个函数传入了effect的第二个参数。
stop是已经定义好的方法,当执行了stop并且传入了runner,结果发现onStop执行了。
看下stop方法
js
export function stop(runner: ReactiveEffectRunner) {
runner.effect.stop()
}
再来会议一下这个runner是什么。
js
export function effect<T = any>(
fn: () => T,
options?: ReactiveEffectOptions
): ReactiveEffectRunner {
const _effect = new ReactiveEffect(fn)
if (options) {
extend(_effect, options)
if (options.scope) recordEffectScope(_effect, options.scope)
}
const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
runner.effect = _effect
return runner
}
runner其实就是_effect.run
(触发副作用),把_effect
保存到了runner.effect上面了。而上面的stop的方法接受参数runner,然后拿到了runner的effect调用了stop。
在调用了stop中执行了这样的代码
js
stop() {
if (this.active) {
cleanupEffect(this)
if (this.onStop) {
this.onStop()
}
this.active = false
}
}
如果this.onStop
存在在执行this.onStop
由于extend(_effect, options)
所以effect的第二个参数{onStop}
会被合并到实例上面,所以onStop会被调用
computed篇
测试一:实现基本功能(缓存)
js
it('should return updated value', () => {
const value = reactive<{ foo?: number }>({})
const cValue = computed(() => value.foo)
expect(cValue.value).toBe(undefined)
value.foo = 1
expect(cValue.value).toBe(1)
})
我们可以看到这里的computed的作用和effect相当,不过拿到这个计算属性的值需要.value
,那么可以直接说明类里有get value()方法。
js
/packages/reactivity/src/computed.ts
export function computed<T>(
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
debugOptions?: DebuggerOptions
) {
let getter: ComputedGetter<T>
···
const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter)
···
return cRef as any
}
computed返回一个由new ComputedRefImpl
创建的实例, getter就是computed的第一个参数。
js
class ComputedRefImpl<T> {
public dep?: Dep = undefined
private _value!: T
private _dirty = true
public readonly effect: ReactiveEffect<T>
public readonly __v_isRef = true
public readonly [ReactiveFlags.IS_READONLY]: boolean
constructor(
getter: ComputedGetter<T>,
private readonly _setter: ComputedSetter<T>,
isReadonly: boolean
) {
this.effect = new ReactiveEffect(getter, () => {
if (!this._dirty) {
this._dirty = true
triggerRefValue(this)
}
})
this[ReactiveFlags.IS_READONLY] = isReadonly
}
get value() {
// the computed ref may get wrapped by other proxies e.g. readonly() #3376
const self = toRaw(this)
if (self._dirty) {
self._dirty = false
self._value = self.effect.run()!
}
return self._value
}
set value(newValue: T) {
this._setter(newValue)
}
}
new ComputedRefImpl
构造函数会new ReactiveEffect
创建一个副作用。
当访问由ComputedRefImpl
类创建的实例的value的时候(所以访问计算属性的值得时候需要.value),会触发get
js
if (self._dirty) {
self._dirty = false
self._value = self.effect.run()!
}
这一步操作,我认为是computed的精髓。_dirty默认是true
首先会判断_dirty是否为true,第一次的时候肯定是true,接着就把这个值设置为fasle,然后把self.effect.run()
返回结果赋值给 self._value
执行run的时候就会访问响应式数据的值去收集依赖。
然后返回 self._value
js
it('should compute lazily', () => {
const value = reactive<{ foo?: number }>({})
const getter = jest.fn(() => value.foo)
const cValue = computed(getter)
// lazy
expect(getter).not.toHaveBeenCalled()
expect(cValue.value).toBe(undefined)
expect(getter).toHaveBeenCalledTimes(1)
// should not compute again
cValue.value
expect(getter).toHaveBeenCalledTimes(1)
// should not compute until needed
value.foo = 1
expect(getter).toHaveBeenCalledTimes(1)
// now it should compute
expect(cValue.value).toBe(1)
expect(getter).toHaveBeenCalledTimes(2)
// should not compute again
cValue.value
expect(getter).toHaveBeenCalledTimes(2)
})
这个测试说明,当第一次访问cValue.value
的时候,触发了get,首次触发的时候,_dirty由true变成了false。当value.foo
发生了变化的时候,则会去触发依赖的更新。
这里的触发依赖了reactive创建的响应式数据foo的依赖。
由于new ReactiveEffect
有第二个参数scheduler
,所以依赖更新的时候会执行scheduler
,
js
() => {
if (!this._dirty) {
this._dirty = true
}
})
这里如果_dirty为false的时候, 则将设置成true。
当再次的访问computed的时候,会触发get,由于_dirty此时变成了true 所以会再次执行self._value = self.effect.run()!
重新赋值。
如果连续访问computed的value的时候,而computed依赖的响应式数据没有发生变化,_dirty只有第一次访问的时候为true,后面再访问就是false,拿到的也就是第一次访问的值,这就建立了缓存
简而言之就是:通过effect的scheduler设置来实现的,我们第一次去获取computed计算属性,会直接执行一次fn()
,而后修改响应式的值再进行获取,触发的是set
的trigger
进行执行,先会去执行scheduler,那会使得dirty的值发生改变,使得获取数据不再是缓存。
isReactive
js
test('Object', () => {
const original = { foo: 1 }
const observed = reactive(original)
expect(isReactive(observed)).toBe(true)
expect(isReactive(original)).toBe(false)
})
可以检查对象是否是由 reactive创建的响应式代理。
找到isReactive定义的位置
js
/packages/reactivity/src/reactive.ts
export const enum ReactiveFlags {
SKIP = '__v_skip',
IS_REACTIVE = '__v_isReactive',
IS_READONLY = '__v_isReadonly',
RAW = '__v_raw'
}
export function isReactive(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])
}
其实就是访问value的ReactiveFlags.IS_REACTIVE
这个属性,然后就会触发代理对象中的get
js
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 (
key === ReactiveFlags.RAW &&
receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap
).get(target)
) {
return target
}
}
}
可以看到如果key是ReactiveFlags.IS_REACTIVE
则返回!isReadonly
看下刚刚的例子,因为observed是调用reactive创建的对象,所以会在baseHandlers.ts文件中会走
js
const get = /*#__PURE__*/ createGetter()
因为isReadonly没有传参所以默认为false所以此时访问的ReactiveFlags.IS_REACTIVE
返回的!isReadonly
为true
而isReactive(original) 由于original[ReactiveFlags.IS_REACTIVE]
是undefined ,所以返回false
isReadonly
js
it('should make nested values readonly', () => {
const original = { foo: 1, bar: { baz: 2 } }
const wrapped = readonly(original)
expect(isReactive(wrapped)).toBe(false)
expect(isReadonly(wrapped)).toBe(true)
expect(isReactive(original)).toBe(false)
expect(isReadonly(original)).toBe(false)
expect(isReactive(wrapped.bar)).toBe(false)
expect(isReadonly(wrapped.bar)).toBe(true)
expect(isReactive(original.bar)).toBe(false)
expect(isReadonly(original.bar)).toBe(false)
})
检查对象是否是由readonly创建的只读代理
isReadonly处理的方式和isReactive类似,isReadonly访问的是ReactiveFlags.IS_READONLY
js
export function isReadonly(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
}
此时应该调用代理对象中的
js
const readonlyGet = /*#__PURE__*/ createGetter(true)
回忆一下createGetter,此时isReadonly是true,所以当key为ReactiveFlags.IS_READONLY
直接返回isReadonly 则会true
嵌套的对象是当在访问的时候判断是否是对象递归处理
js
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)
}
上述功能说白了就是利用了proxy
代理对象的性质,既然没有在类里定义(多种情况,定义了也无济于事),访问属性,那么key
值就暴露出来了。
isRef
js
test('isRef', () => {
expect(isRef(ref(1))).toBe(true)
expect(isRef(computed(() => 1))).toBe(true)
expect(isRef(0)).toBe(false)
expect(isRef(1)).toBe(false)
// an object that looks like a ref isn't necessarily a ref
expect(isRef({ value: 0 })).toBe(false)
})
检查值是否为一个 ref 对象
js
export function isRef(r: any): r is Ref {
return Boolean(r && r.__v_isRef === true)
}
其实这个和isReactive的实现方式差不多,isRef接受一个对象,判断这个对象的__v_isRef
是否存在
回忆一下
js
class RefImpl<T> {
private _value: T
private _rawValue: T
public dep?: Dep = undefined
public readonly __v_isRef = true
constructor(value: T, public readonly _shallow: boolean) {
···
}
get value() {
···
}
set value(newVal) {
···
}
}
ref其实就是一个工厂函数返回的对象实例,这个对象实例是通过new RefTmpl
创建的。
这个类中有一个不可更改的属性 __v_isRef = true
所以只要是ref创建的值,都会有这个属性。
unRef
js
test('unref', () => {
expect(unref(1)).toBe(1)
expect(unref(ref(1))).toBe(1)
})
如果参数是一个 ref,则返回内部值,否则返回参数本身
js
export function unref<T>(ref: T | Ref<T>): T {
return isRef(ref) ? (ref.value as any) : ref
}
readonly
首先看下测试
js
it('should make nested values readonly', () => {
const original = { foo: 1, bar: { baz: 2 } }
const wrapped = readonly(original)
expect(wrapped).not.toBe(original)
expect(isReadonly(wrapped)).toBe(true)
expect(isReadonly(original)).toBe(false)
expect(isReadonly(wrapped.bar)).toBe(true)
expect(isReadonly(original.bar)).toBe(false)
// get
expect(wrapped.foo).toBe(1)
// has
expect('foo' in wrapped).toBe(true)
// ownKeys
expect(Object.keys(wrapped)).toEqual(['foo', 'bar'])
})
it('should not allow mutation', () => {
const qux = Symbol('qux')
const original = {
foo: 1,
bar: {
baz: 2
},
[qux]: 3
}
const wrapped: Writable<typeof original> = readonly(original)
wrapped.foo = 2
expect(wrapped.foo).toBe(1)
expect(
`Set operation on key "foo" failed: target is readonly.`
).toHaveBeenWarnedLast()
wrapped.bar.baz = 3
expect(wrapped.bar.baz).toBe(2)
expect(
`Set operation on key "baz" failed: target is readonly.`
).toHaveBeenWarnedLast()
})
Readonly的意思是我们只可以访问值,却不可以修改值
js
export function readonly<T extends object>(
target: T
): DeepReadonly<UnwrapNestedRefs<T>> {
return createReactiveObject(
target,
true,
readonlyHandlers,
readonlyCollectionHandlers,
readonlyMap
)
}
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
// 如果已经是响应式对象,则直接返回,避免重复创建
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// 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
}
// 响应式处理: 利用Proxy做代理
// vue2
// Object.defineProperty
// COLLECTION: Map Set
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}
其实差不多和reactive的处理方式是一样的,只不过第二个参数isReadonly是true
baseHandlers传的是readonlyHandlers,所以当访问用readonly包裹的对象的时候,proxy里面的拦截是用readonlyHandlers拦截的
js
export const readonlyHandlers: ProxyHandler<object> = {
get: readonlyGet,
set(target, key) {
if (__DEV__) {
console.warn(
`Set operation on key "${String(key)}" failed: target is readonly.`,
target
)
}
return true
},
deleteProperty(target, key) {
if (__DEV__) {
console.warn(
`Delete operation on key "${String(key)}" failed: target is readonly.`,
target
)
}
return true
}
}
可以看到,当访问到对象的时候,会触发readonlyGet,而当修改对象,对着删除对象中的属性的时候,都会发出警告,所以由此可以断定,readonly只能访问不能修改和删除。
js
const readonlyGet = /*#__PURE__*/ createGetter(true)
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
const targetIsArray = isArray(target)
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 (shallow) {
return res
}
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
}
}
这里首先返回res,然后判断是否是isReadonly,因为readonly是不允许修改的,所以就没有必要去触发依赖更新,那也就没有必要去收集依赖了。下面判断res是否是对象,来处理嵌套对象的,所以当readonly包裹的对象是一个嵌套的,那对象的每一层都是readonly的状态