| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 |
- 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)
- }
- class BaseReactiveHandler implements ProxyHandler<Target> {
- constructor(
- protected readonly _isReadonly = false,
- protected readonly _isShallow = false,
- ) {}
- get(target: Target, key: string | symbol, receiver: object): any {
- if (key === ReactiveFlags.SKIP) return target[ReactiveFlags.SKIP]
- 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) {
- 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
- }
- // early return undefined
- return
- }
- 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.
- const value = targetIsArray && isIntegerKey(key) ? res : res.value
- return isReadonly && isObject(value) ? readonly(value) : 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]
- const isArrayWithIntegerKey = isArray(target) && isIntegerKey(key)
- if (!this._isShallow) {
- const isOldValueReadonly = isReadonly(oldValue)
- if (!isShallow(value) && !isReadonly(value)) {
- oldValue = toRaw(oldValue)
- value = toRaw(value)
- }
- if (!isArrayWithIntegerKey && isRef(oldValue) && !isRef(value)) {
- if (isOldValueReadonly) {
- if (__DEV__) {
- warn(
- `Set operation on key "${String(key)}" failed: target is readonly.`,
- target[key],
- )
- }
- return true
- } else {
- oldValue.value = value
- return true
- }
- }
- } else {
- // in shallow mode, objects are set as-is regardless of reactive or not
- }
- const hadKey = isArrayWithIntegerKey
- ? 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)
|