reactive
-
实现源码在packsages/reactivity/reactive.ts
-
-
目录结构
── src
├── baseHandlers.ts // 基本类型的处理器
├── collectionHandlers.ts // Set Map WeakSet WeckMap的处理器
├── computed.ts // 计算属性,同Vue2
├── effect.ts // reactive 核心,处理依赖收集,依赖更新
├── index.ts
├── operations.ts // 定义依赖收集,依赖更新的类型
├── reactive.ts // reactive 入口,内部主要以Proxy实现
└── ref.ts // reactive 的变种方法,Proxy处理不了值类型的响应,Ref来处理
- reactive
- reactive是Vue3中响应数据核心
- reactive 中的实现是由 proxy 加 effect 组合
// reactive.ts
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
// 如果目标对象是一个只读的响应数据,则直接返回目标对象
// if trying to observe a readonly proxy, return the readonly version.
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
// 否则调用createReactiveObject创建 observe
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
}
// createReactiveObject
function createReactiveObject(
target: Target, // 目标对象
isReadonly: boolean, // 是否只读
baseHandlers: ProxyHandler<any>, // 基本类型的 handlers
collectionHandlers: ProxyHandler<any>, // 主要对(set、map、weakSet、weakMap)的handlers
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
// 如果目标对象已经是个proxy直接返回
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
}
// 通过new Proxy创建 observe
// 如果是普通的对象 Object 或 Array,处理器对象就使用 baseHandlers,如果是 Set, Map, WeakMap, WeakSet 中的一个,就使用 collectionHandlers。
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
// 将原始对象和代理对象放入proxyMap
proxyMap.set(target, proxy)
return proxy
}
//获取观察种类
function getTargetType(value: Target) {
// 被打上__v_skip标记或者改对象不可扩展(不可添加新的属性)返回0
return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
? TargetType.INVALID
: targetTypeMap(toRawType(value))
}
export const enum ReactiveFlags {
SKIP = '__v_skip',
IS_REACTIVE = '__v_isReactive',
IS_READONLY = '__v_isReadonly',
RAW = '__v_raw'
}
const enum TargetType {
INVALID = 0,
COMMON = 1,
COLLECTION = 2
}
// 根据类型判断是否可观察,可以被观察的:Object,Array,Map,Set,WeakMap,WeakSet
function targetTypeMap(rawType: string) {
switch (rawType) {
case 'Object':
case 'Array':
return TargetType.COMMON
case 'Map':
case 'Set':
case 'WeakMap':
case 'WeakSet':
return TargetType.COLLECTION
default:
return TargetType.INVALID
}
}
- reactive 是做为整个响应式的入口,负责处理目标对象是否可观察以及是否已被观察的逻辑,最后使用 Proxy 进行目标对象的代理
- 注意:
export const reactiveMap = new WeakMap<Target, any>()
,reactiveMap使用的是 WeakMap,WeakMap 的键值必须是对象,而且 WeakMap 的键值是不可枚举的,是弱引用。方便垃圾回收。- 弱引用:当它的键所指对象没有被其他地方引用时,就会被垃圾回收了
- 对应的是垃圾回收机制中的标记清除法
- 强引用:当一个对象被创建时,计数为1,每有一个变量引用该对象,计数都会加1,只有当计数为0时,对象才会被垃圾回收。所以一旦造成循环引用等问题,就会造成内存泄漏。
- 对应的是垃圾回收机制中的引用计数法
reactive.spec 单元测试
- 单纯用来熟悉下Object变化的情况,熟悉可跳过
test('Object', () => {
const original = { foo: 1 }
const observed = reactive(original)
expect(observed).not.toBe(original)
// isReactive检查一个对象是否是由 reactive创建的响应式代理。
expect(isReactive(observed)).toBe(true)
expect(isReactive(original)).toBe(false)
// get
expect(observed.foo).toBe(1)
// has
expect('foo' in observed).toBe(true)
// ownKeys
expect(Object.keys(observed)).toEqual(['foo'])
})
test('nested reactives', () => {
const original = {
nested: {
foo: 1
},
array: [{ bar: 2 }]
}
const observed = reactive(original)
// 嵌套的属性也可以响应
expect(isReactive(observed.nested)).toBe(true)
expect(isReactive(observed.array)).toBe(true)
expect(isReactive(observed.array[0])).toBe(true)
})
test('observed value should proxy mutations to original (Object)', () => {
//代理对象属性变化会同步至原始对象
const original: any = { foo: 1 }
const observed = reactive(original)
// set
observed.bar = 1
expect(observed.bar).toBe(1)
expect(original.bar).toBe(1)
// delete
delete observed.foo
expect('foo' in observed).toBe(false)
expect('foo' in original).toBe(false)
})
test('setting a property with an unobserved value should wrap with reactive', () => {
// 给observed设置一个未被观察的值依然是响应式的(修复vue2.x中的问题)
const observed = reactive<{ foo?: object }>({})
const raw = {}
observed.foo = raw
expect(observed.foo).not.toBe(raw)
expect(isReactive(observed.foo)).toBe(true)
})
test('observing already observed value should return same Proxy', () => {
// 观察一个已经被observed的observe应该直接返回该observe
const original = { foo: 1 }
const observed = reactive(original)
const observed2 = reactive(observed)
expect(observed2).toBe(observed)
})
test('observing the same value multiple times should return same Proxy', () => {
// 观察已被observed的对象直接返回代理对象
const original = { foo: 1 }
const observed = reactive(original)
const observed2 = reactive(original)
expect(observed2).toBe(observed)
})
test('should not pollute original object with Proxies', () => {
// 修改代理对象的值不会污染原始对象
const original: any = { foo: 1 }
const original2 = { bar: 2 }
const observed = reactive(original)
const observed2 = reactive(original2)
observed.bar = observed2
expect(observed.bar).toBe(observed2)
expect(original.bar).toBe(original2)
})
test('unwrap', () => {
const original = { foo: 1 }
const observed = reactive(original)
// toRaw返回被观察对象的原始对象
expect(toRaw(observed)).toBe(original)
expect(toRaw(original)).toBe(original)
})
test('should not unwrap Ref<T>', () => {
// 返回值为Ref
const observedNumberRef = reactive(ref(1))
const observedObjectRef = reactive(ref({ foo: 1 }))
expect(isRef(observedNumberRef)).toBe(true)
expect(isRef(observedObjectRef)).toBe(true)
})
// 测试各类型能否被观察
test('non-observable values', () => {
const assertValue = (value: any) => {
reactive(value)
expect(
`value cannot be made reactive: ${String(value)}`
).toHaveBeenWarnedLast()
}
// number
assertValue(1)
// string
assertValue('foo')
// boolean
assertValue(false)
// null
assertValue(null)
// undefined
assertValue(undefined)
// symbol
const s = Symbol()
assertValue(s)
// built-ins should work and return same value
const p = Promise.resolve()
expect(reactive(p)).toBe(p)
const r = new RegExp('')
expect(reactive(r)).toBe(r)
const d = new Date()
expect(reactive(d)).toBe(d)
})
test('markRaw', () => {
// markRaw 可以给将要被观察的数据打上标记,标记原始数据不可被观察
// 标记不可被观察的数据不可观察
const obj = reactive({
foo: { a: 1 },
bar: markRaw({ b: 2 })
})
expect(isReactive(obj.foo)).toBe(true)
expect(isReactive(obj.bar)).toBe(false)
})
//被freeze冻结的对象不可观察
test('should not observe frozen objects', () => {
const obj = reactive({
foo: Object.freeze({ a: 1 })
})
expect(isReactive(obj.foo)).toBe(false)
})
// shallowReactive:只为某个对象的私有(第一层)属性创建浅层的响应式代理,不会对"属性的属性"做深层次、递归地响应式代理
test('should keep reactive properties reactive', () => {
// 属性的属性不可观察
const props: any = shallowReactive({ n: reactive({ foo: 1 }) })
props.n = reactive({ foo: 2 })
expect(isReactive(props.n)).toBe(true)
})
test('should keep reactive properties reactive', () => {
// shallowReactive后的proxy的属性若再次被reactive则可被观察
const props: any = shallowReactive({ n: reactive({ foo: 1 }) })
props.n = reactive({ foo: 2 })
expect(isReactive(props.n)).toBe(true)
})
test('should not observe when iterating', () => {
// 通过...迭代出不可观察
const shallowSet = shallowReactive(new Set())
const a = {}
shallowSet.add(a)
const spreadA = [...shallowSet][0]
expect(isReactive(spreadA)).toBe(false)
})
test('should not get reactive entry', () => {
// get到的属性不可观察
const shallowMap = shallowReactive(new Map())
const a = {}
const key = 'a'
shallowMap.set(key, a)
expect(isReactive(shallowMap.get(key))).toBe(false)
})
test('should not get reactive on foreach', () => {
// 循环出的每一项不可观察
const shallowSet = shallowReactive(new Set())
const a = {}
shallowSet.add(a)
shallowSet.forEach(x => expect(isReactive(x)).toBe(false))
})
// shallowRef可以强制触发更新
test('shallowRef force trigger', () => {
const sref = shallowRef({ a: 1 })
let dummy
effect(() => {
dummy = sref.value.a
})
expect(dummy).toBe(1)
sref.value.a = 2
expect(dummy).toBe(1) // should not trigger yet
// force trigger
triggerRef(sref)
expect(dummy).toBe(2)
})
//isRef判断是否为ref
test('isRef', () => {
expect(isRef(ref(1))).toBe(true)
expect(isRef(computed(() => 1))).toBe(true)
expect(isRef(0)).toBe(false)
expect(isRef(1)).toBe(false)
expect(isRef({ value: 0 })).toBe(false)
})
// customRef支持自定义 ref, 自由控制 track, trigger 时间
test('customRef', () => {
let value = 1
let _trigger: () => void
const custom = customRef((track, trigger) => ({
get() {
track()
return value
},
set(newValue: number) {
value = newValue
_trigger = trigger
}
}))
expect(isRef(custom)).toBe(true)
let dummy
effect(() => {
dummy = custom.value
})
expect(dummy).toBe(1)
custom.value = 2
// should not trigger yet
expect(dummy).toBe(1)
_trigger!()
expect(dummy).toBe(2)
})