fix(reactivity): track reactive keys in raw collection types

Also warn against presence of both raw and reactive versions of the
same object in a collection as keys.

fix #919
This commit is contained in:
Evan You
2020-04-04 12:57:15 -04:00
parent 7402951d94
commit 5dcc645fc0
3 changed files with 126 additions and 6 deletions

View File

@@ -2,7 +2,13 @@ import { toRaw, reactive, readonly } from './reactive'
import { track, trigger, ITERATE_KEY, MAP_KEY_ITERATE_KEY } from './effect'
import { TrackOpTypes, TriggerOpTypes } from './operations'
import { LOCKED } from './lock'
import { isObject, capitalize, hasOwn, hasChanged } from '@vue/shared'
import {
isObject,
capitalize,
hasOwn,
hasChanged,
toRawType
} from '@vue/shared'
export type CollectionTypes = IterableCollections | WeakCollections
@@ -27,6 +33,9 @@ function get(
) {
target = toRaw(target)
const rawKey = toRaw(key)
if (key !== rawKey) {
track(target, TrackOpTypes.GET, key)
}
track(target, TrackOpTypes.GET, rawKey)
const { has, get } = getProto(target)
if (has.call(target, key)) {
@@ -39,6 +48,9 @@ function get(
function has(this: CollectionTypes, key: unknown): boolean {
const target = toRaw(this)
const rawKey = toRaw(key)
if (key !== rawKey) {
track(target, TrackOpTypes.HAS, key)
}
track(target, TrackOpTypes.HAS, rawKey)
const has = getProto(target).has
return has.call(target, key) || has.call(target, rawKey)
@@ -64,12 +76,19 @@ function add(this: SetTypes, value: unknown) {
function set(this: MapTypes, key: unknown, value: unknown) {
value = toRaw(value)
key = toRaw(key)
const target = toRaw(this)
const proto = getProto(target)
const hadKey = proto.has.call(target, key)
const oldValue = proto.get.call(target, key)
const result = proto.set.call(target, key, value)
const { has, get, set } = getProto(target)
let hadKey = has.call(target, key)
if (!hadKey) {
key = toRaw(key)
hadKey = has.call(target, key)
} else if (__DEV__) {
checkIdentitiyKeys(target, has, key)
}
const oldValue = get.call(target, key)
const result = set.call(target, key, value)
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
@@ -85,7 +104,10 @@ function deleteEntry(this: CollectionTypes, key: unknown) {
if (!hadKey) {
key = toRaw(key)
hadKey = has.call(target, key)
} else if (__DEV__) {
checkIdentitiyKeys(target, has, key)
}
const oldValue = get ? get.call(target, key) : undefined
// forward the operation before queueing reactions
const result = del.call(target, key)
@@ -251,3 +273,21 @@ export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {
export const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {
get: createInstrumentationGetter(readonlyInstrumentations)
}
function checkIdentitiyKeys(
target: CollectionTypes,
has: (key: unknown) => boolean,
key: unknown
) {
const rawKey = toRaw(key)
if (rawKey !== key && has.call(target, rawKey)) {
const type = toRawType(target)
console.warn(
`Reactive ${type} contains both the raw and reactive ` +
`versions of the same object${type === `Map` ? `as keys` : ``}, ` +
`which can lead to inconsistencies. ` +
`Avoid differentiating between the raw and reactive versions ` +
`of an object and only use the reactive version if possible.`
)
}
}