refactor(reactivity): use more efficient reactive checks
WeakSets and WeakMaps shows degrading performance as the amount of observed objects increases. Using hidden keys result in better performance especially when repeatedly creating large amounts of reactive proxies. This also makes it possible to more efficiently declare non-reactive objects in userland.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { isObject, toRawType } from '@vue/shared'
|
||||
import { isObject, toRawType, def } from '@vue/shared'
|
||||
import {
|
||||
mutableHandlers,
|
||||
readonlyHandlers,
|
||||
@@ -13,25 +13,38 @@ import { UnwrapRef, Ref } from './ref'
|
||||
import { makeMap } from '@vue/shared'
|
||||
|
||||
// WeakMaps that store {raw <-> observed} pairs.
|
||||
const rawToReactive = new WeakMap<any, any>()
|
||||
const reactiveToRaw = new WeakMap<any, any>()
|
||||
const rawToReadonly = new WeakMap<any, any>()
|
||||
const readonlyToRaw = new WeakMap<any, any>()
|
||||
// const rawToReactive = new WeakMap<any, any>()
|
||||
// const reactiveToRaw = new WeakMap<any, any>()
|
||||
// const rawToReadonly = new WeakMap<any, any>()
|
||||
// const readonlyToRaw = new WeakMap<any, any>()
|
||||
|
||||
// WeakSets for values that are marked readonly or non-reactive during
|
||||
// observable creation.
|
||||
const rawValues = new WeakSet<any>()
|
||||
export const enum ReactiveFlags {
|
||||
skip = '__v_skip',
|
||||
isReactive = '__v_isReactive',
|
||||
isReadonly = '__v_isReadonly',
|
||||
raw = '__v_raw',
|
||||
reactive = '__v_reactive',
|
||||
readonly = '__v_readonly'
|
||||
}
|
||||
|
||||
interface Target {
|
||||
__v_skip?: boolean
|
||||
__v_isReactive?: boolean
|
||||
__v_isReadonly?: boolean
|
||||
__v_raw?: any
|
||||
__v_reactive?: any
|
||||
__v_readonly?: any
|
||||
}
|
||||
|
||||
const collectionTypes = new Set<Function>([Set, Map, WeakMap, WeakSet])
|
||||
const isObservableType = /*#__PURE__*/ makeMap(
|
||||
'Object,Array,Map,Set,WeakMap,WeakSet'
|
||||
)
|
||||
|
||||
const canObserve = (value: any): boolean => {
|
||||
const canObserve = (value: Target): boolean => {
|
||||
return (
|
||||
!value._isVNode &&
|
||||
!value.__v_skip &&
|
||||
isObservableType(toRawType(value)) &&
|
||||
!rawValues.has(value) &&
|
||||
!Object.isFrozen(value)
|
||||
)
|
||||
}
|
||||
@@ -42,13 +55,12 @@ type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRef<T>
|
||||
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 (readonlyToRaw.has(target)) {
|
||||
if (target && (target as Target).__v_isReadonly) {
|
||||
return target
|
||||
}
|
||||
return createReactiveObject(
|
||||
target,
|
||||
rawToReactive,
|
||||
reactiveToRaw,
|
||||
false,
|
||||
mutableHandlers,
|
||||
mutableCollectionHandlers
|
||||
)
|
||||
@@ -60,8 +72,7 @@ export function reactive(target: object) {
|
||||
export function shallowReactive<T extends object>(target: T): T {
|
||||
return createReactiveObject(
|
||||
target,
|
||||
rawToReactive,
|
||||
reactiveToRaw,
|
||||
false,
|
||||
shallowReactiveHandlers,
|
||||
mutableCollectionHandlers
|
||||
)
|
||||
@@ -72,8 +83,7 @@ export function readonly<T extends object>(
|
||||
): Readonly<UnwrapNestedRefs<T>> {
|
||||
return createReactiveObject(
|
||||
target,
|
||||
rawToReadonly,
|
||||
readonlyToRaw,
|
||||
true,
|
||||
readonlyHandlers,
|
||||
readonlyCollectionHandlers
|
||||
)
|
||||
@@ -88,17 +98,15 @@ export function shallowReadonly<T extends object>(
|
||||
): Readonly<{ [K in keyof T]: UnwrapNestedRefs<T[K]> }> {
|
||||
return createReactiveObject(
|
||||
target,
|
||||
rawToReadonly,
|
||||
readonlyToRaw,
|
||||
true,
|
||||
shallowReadonlyHandlers,
|
||||
readonlyCollectionHandlers
|
||||
)
|
||||
}
|
||||
|
||||
function createReactiveObject(
|
||||
target: unknown,
|
||||
toProxy: WeakMap<any, any>,
|
||||
toRaw: WeakMap<any, any>,
|
||||
target: Target,
|
||||
isReadonly: boolean,
|
||||
baseHandlers: ProxyHandler<any>,
|
||||
collectionHandlers: ProxyHandler<any>
|
||||
) {
|
||||
@@ -108,15 +116,16 @@ function createReactiveObject(
|
||||
}
|
||||
return target
|
||||
}
|
||||
// target is already a Proxy, return it.
|
||||
// excpetion: calling readonly() on a reactive object
|
||||
if (target.__v_raw && !(isReadonly && target.__v_isReactive)) {
|
||||
return target
|
||||
}
|
||||
// target already has corresponding Proxy
|
||||
let observed = toProxy.get(target)
|
||||
let observed = isReadonly ? target.__v_readonly : target.__v_reactive
|
||||
if (observed !== void 0) {
|
||||
return observed
|
||||
}
|
||||
// target is already a Proxy
|
||||
if (toRaw.has(target)) {
|
||||
return target
|
||||
}
|
||||
// only a whitelist of value types can be observed.
|
||||
if (!canObserve(target)) {
|
||||
return target
|
||||
@@ -125,30 +134,34 @@ function createReactiveObject(
|
||||
? collectionHandlers
|
||||
: baseHandlers
|
||||
observed = new Proxy(target, handlers)
|
||||
toProxy.set(target, observed)
|
||||
toRaw.set(observed, target)
|
||||
def(
|
||||
target,
|
||||
isReadonly ? ReactiveFlags.readonly : ReactiveFlags.reactive,
|
||||
observed
|
||||
)
|
||||
return observed
|
||||
}
|
||||
|
||||
export function isReactive(value: unknown): boolean {
|
||||
value = readonlyToRaw.get(value) || value
|
||||
return reactiveToRaw.has(value)
|
||||
if (isReadonly(value)) {
|
||||
return isReactive((value as Target).__v_raw)
|
||||
}
|
||||
return !!(value && (value as Target).__v_isReactive)
|
||||
}
|
||||
|
||||
export function isReadonly(value: unknown): boolean {
|
||||
return readonlyToRaw.has(value)
|
||||
return !!(value && (value as Target).__v_isReadonly)
|
||||
}
|
||||
|
||||
export function isProxy(value: unknown): boolean {
|
||||
return readonlyToRaw.has(value) || reactiveToRaw.has(value)
|
||||
return isReactive(value) || isReadonly(value)
|
||||
}
|
||||
|
||||
export function toRaw<T>(observed: T): T {
|
||||
observed = readonlyToRaw.get(observed) || observed
|
||||
return reactiveToRaw.get(observed) || observed
|
||||
return (observed && toRaw((observed as Target).__v_raw)) || observed
|
||||
}
|
||||
|
||||
export function markRaw<T extends object>(value: T): T {
|
||||
rawValues.add(value)
|
||||
def(value, ReactiveFlags.skip, true)
|
||||
return value
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user