mirror of https://github.com/vuejs/core.git
263 lines
7.1 KiB
TypeScript
263 lines
7.1 KiB
TypeScript
import {
|
|
type Target,
|
|
isReadonly,
|
|
isShallow,
|
|
reactive,
|
|
reactiveMap,
|
|
readonly,
|
|
readonlyMap,
|
|
shallowReactiveMap,
|
|
shallowReadonlyMap,
|
|
toRaw,
|
|
} from './reactive'
|
|
import { arrayInstrumentations } from './arrayInstrumentations'
|
|
import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants'
|
|
import { ITERATE_KEY, track, trigger } from './dep'
|
|
import {
|
|
hasChanged,
|
|
hasOwn,
|
|
isArray,
|
|
isIntegerKey,
|
|
isObject,
|
|
isSymbol,
|
|
makeMap,
|
|
} from '@vue/shared'
|
|
import { isRef } from './ref'
|
|
import { warn } from './warning'
|
|
|
|
const isNonTrackableKeys = /*@__PURE__*/ makeMap(`__proto__,__v_isRef,__isVue`)
|
|
|
|
const builtInSymbols = new Set(
|
|
/*@__PURE__*/
|
|
Object.getOwnPropertyNames(Symbol)
|
|
// ios10.x Object.getOwnPropertyNames(Symbol) can enumerate 'arguments' and 'caller'
|
|
// but accessing them on Symbol leads to TypeError because Symbol is a strict mode
|
|
// function
|
|
.filter(key => key !== 'arguments' && key !== 'caller')
|
|
.map(key => Symbol[key as keyof SymbolConstructor])
|
|
.filter(isSymbol),
|
|
)
|
|
|
|
function hasOwnProperty(this: object, key: unknown) {
|
|
// #10455 hasOwnProperty may be called with non-string values
|
|
if (!isSymbol(key)) key = String(key)
|
|
const obj = toRaw(this)
|
|
track(obj, TrackOpTypes.HAS, key)
|
|
return obj.hasOwnProperty(key as string)
|
|
}
|
|
|
|
export function getRawValue(
|
|
receiver: object,
|
|
isReadonly: boolean,
|
|
isShallow: boolean,
|
|
target: Target,
|
|
): Target | undefined {
|
|
if (
|
|
receiver ===
|
|
(isReadonly
|
|
? isShallow
|
|
? shallowReadonlyMap
|
|
: readonlyMap
|
|
: isShallow
|
|
? shallowReactiveMap
|
|
: reactiveMap
|
|
).get(target) ||
|
|
// receiver is not the reactive proxy, but has the same prototype
|
|
// this means the receiver is a user proxy of the reactive proxy
|
|
Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)
|
|
) {
|
|
return target
|
|
}
|
|
}
|
|
|
|
class BaseReactiveHandler implements ProxyHandler<Target> {
|
|
constructor(
|
|
protected readonly _isReadonly = false,
|
|
protected readonly _isShallow = false,
|
|
) {}
|
|
|
|
get(target: Target, key: string | symbol, receiver: object): any {
|
|
const isReadonly = this._isReadonly,
|
|
isShallow = this._isShallow
|
|
if (key === ReactiveFlags.IS_REACTIVE) {
|
|
return !isReadonly
|
|
} else if (key === ReactiveFlags.IS_READONLY) {
|
|
return isReadonly
|
|
} else if (key === ReactiveFlags.IS_SHALLOW) {
|
|
return isShallow
|
|
} else if (key === ReactiveFlags.RAW) {
|
|
return getRawValue(receiver, isReadonly, isShallow, target)
|
|
}
|
|
|
|
const targetIsArray = isArray(target)
|
|
|
|
if (!isReadonly) {
|
|
let fn: Function | undefined
|
|
if (targetIsArray && (fn = arrayInstrumentations[key])) {
|
|
return fn
|
|
}
|
|
if (key === 'hasOwnProperty') {
|
|
return hasOwnProperty
|
|
}
|
|
}
|
|
|
|
const res = Reflect.get(
|
|
target,
|
|
key,
|
|
// if this is a proxy wrapping a ref, return methods using the raw ref
|
|
// as receiver so that we don't have to call `toRaw` on the ref in all
|
|
// its class methods
|
|
isRef(target) ? target : receiver,
|
|
)
|
|
|
|
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
|
|
return res
|
|
}
|
|
|
|
if (!isReadonly) {
|
|
track(target, TrackOpTypes.GET, key)
|
|
}
|
|
|
|
if (isShallow) {
|
|
return res
|
|
}
|
|
|
|
if (isRef(res)) {
|
|
// ref unwrapping - skip unwrap for Array + integer key.
|
|
return targetIsArray && isIntegerKey(key) ? res : res.value
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
class MutableReactiveHandler extends BaseReactiveHandler {
|
|
constructor(isShallow = false) {
|
|
super(false, isShallow)
|
|
}
|
|
|
|
set(
|
|
target: Record<string | symbol, unknown>,
|
|
key: string | symbol,
|
|
value: unknown,
|
|
receiver: object,
|
|
): boolean {
|
|
let oldValue = target[key]
|
|
if (!this._isShallow) {
|
|
const isOldValueReadonly = isReadonly(oldValue)
|
|
if (!isShallow(value) && !isReadonly(value)) {
|
|
oldValue = toRaw(oldValue)
|
|
value = toRaw(value)
|
|
}
|
|
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
|
|
if (isOldValueReadonly) {
|
|
return false
|
|
} else {
|
|
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,
|
|
isRef(target) ? target : 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
|
|
}
|
|
|
|
deleteProperty(
|
|
target: Record<string | symbol, unknown>,
|
|
key: string | symbol,
|
|
): boolean {
|
|
const hadKey = hasOwn(target, key)
|
|
const oldValue = target[key]
|
|
const result = Reflect.deleteProperty(target, key)
|
|
if (result && hadKey) {
|
|
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
|
|
}
|
|
return result
|
|
}
|
|
|
|
has(target: Record<string | symbol, unknown>, key: string | symbol): boolean {
|
|
const result = Reflect.has(target, key)
|
|
if (!isSymbol(key) || !builtInSymbols.has(key)) {
|
|
track(target, TrackOpTypes.HAS, key)
|
|
}
|
|
return result
|
|
}
|
|
|
|
ownKeys(target: Record<string | symbol, unknown>): (string | symbol)[] {
|
|
track(
|
|
target,
|
|
TrackOpTypes.ITERATE,
|
|
isArray(target) ? 'length' : ITERATE_KEY,
|
|
)
|
|
return Reflect.ownKeys(target)
|
|
}
|
|
}
|
|
|
|
class ReadonlyReactiveHandler extends BaseReactiveHandler {
|
|
constructor(isShallow = false) {
|
|
super(true, isShallow)
|
|
}
|
|
|
|
set(target: object, key: string | symbol) {
|
|
if (__DEV__) {
|
|
warn(
|
|
`Set operation on key "${String(key)}" failed: target is readonly.`,
|
|
target,
|
|
)
|
|
}
|
|
return true
|
|
}
|
|
|
|
deleteProperty(target: object, key: string | symbol) {
|
|
if (__DEV__) {
|
|
warn(
|
|
`Delete operation on key "${String(key)}" failed: target is readonly.`,
|
|
target,
|
|
)
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
export const mutableHandlers: ProxyHandler<object> =
|
|
/*@__PURE__*/ new MutableReactiveHandler()
|
|
|
|
export const readonlyHandlers: ProxyHandler<object> =
|
|
/*@__PURE__*/ new ReadonlyReactiveHandler()
|
|
|
|
export const shallowReactiveHandlers: MutableReactiveHandler =
|
|
/*@__PURE__*/ new MutableReactiveHandler(true)
|
|
|
|
// Props handlers are special in the sense that it should not unwrap top-level
|
|
// refs (in order to allow refs to be explicitly passed down), but should
|
|
// retain the reactivity of the normal readonly object.
|
|
export const shallowReadonlyHandlers: ReadonlyReactiveHandler =
|
|
/*@__PURE__*/ new ReadonlyReactiveHandler(true)
|