refactor(reactivity): refactor iteration key trigger logic + use more robust Map/Set check

This commit is contained in:
Evan You 2020-09-14 11:26:34 -04:00
parent cf1b6c666f
commit 0124eacc91
5 changed files with 48 additions and 28 deletions

View File

@ -6,7 +6,8 @@ import {
capitalize, capitalize,
hasOwn, hasOwn,
hasChanged, hasChanged,
toRawType toRawType,
isMap
} from '@vue/shared' } from '@vue/shared'
export type CollectionTypes = IterableCollections | WeakCollections export type CollectionTypes = IterableCollections | WeakCollections
@ -129,7 +130,7 @@ function clear(this: IterableCollections) {
const target = toRaw(this) const target = toRaw(this)
const hadItems = target.size !== 0 const hadItems = target.size !== 0
const oldTarget = __DEV__ const oldTarget = __DEV__
? target instanceof Map ? isMap(target)
? new Map(target) ? new Map(target)
: new Set(target) : new Set(target)
: undefined : undefined
@ -185,9 +186,10 @@ function createIterableMethod(
): Iterable & Iterator { ): Iterable & Iterator {
const target = (this as any)[ReactiveFlags.RAW] const target = (this as any)[ReactiveFlags.RAW]
const rawTarget = toRaw(target) const rawTarget = toRaw(target)
const isMap = rawTarget instanceof Map const targetIsMap = isMap(rawTarget)
const isPair = method === 'entries' || (method === Symbol.iterator && isMap) const isPair =
const isKeyOnly = method === 'keys' && isMap method === 'entries' || (method === Symbol.iterator && targetIsMap)
const isKeyOnly = method === 'keys' && targetIsMap
const innerIterator = target[method](...args) const innerIterator = target[method](...args)
const wrap = isReadonly ? toReadonly : isShallow ? toShallow : toReactive const wrap = isReadonly ? toReadonly : isShallow ? toShallow : toReactive
!isReadonly && !isReadonly &&

View File

@ -1,5 +1,5 @@
import { TrackOpTypes, TriggerOpTypes } from './operations' import { TrackOpTypes, TriggerOpTypes } from './operations'
import { EMPTY_OBJ, isArray, isIntegerKey } from '@vue/shared' import { EMPTY_OBJ, isArray, isIntegerKey, isMap } from '@vue/shared'
// The main WeakMap that stores {target -> key -> dep} connections. // The main WeakMap that stores {target -> key -> dep} connections.
// Conceptually, it's easier to think of a dependency as a Dep class // Conceptually, it's easier to think of a dependency as a Dep class
@ -197,20 +197,34 @@ export function trigger(
if (key !== void 0) { if (key !== void 0) {
add(depsMap.get(key)) add(depsMap.get(key))
} }
// also run for iteration key on ADD | DELETE | Map.SET // also run for iteration key on ADD | DELETE | Map.SET
const shouldTriggerIteration = switch (type) {
(type === TriggerOpTypes.ADD && case TriggerOpTypes.ADD:
(!isArray(target) || isIntegerKey(key))) || if (!isArray(target)) {
(type === TriggerOpTypes.DELETE && !isArray(target)) add(depsMap.get(ITERATE_KEY))
if ( if (isMap(target)) {
shouldTriggerIteration ||
(type === TriggerOpTypes.SET && target instanceof Map)
) {
add(depsMap.get(isArray(target) ? 'length' : ITERATE_KEY))
}
if (shouldTriggerIteration && target instanceof Map) {
add(depsMap.get(MAP_KEY_ITERATE_KEY)) add(depsMap.get(MAP_KEY_ITERATE_KEY))
} }
} else if (isIntegerKey(key)) {
// new index added to array -> length changes
add(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
case TriggerOpTypes.SET:
if (isMap(target)) {
add(depsMap.get(ITERATE_KEY))
}
break
}
} }
const run = (effect: ReactiveEffect) => { const run = (effect: ReactiveEffect) => {

View File

@ -16,7 +16,9 @@ import {
isString, isString,
hasChanged, hasChanged,
NOOP, NOOP,
remove remove,
isMap,
isSet
} from '@vue/shared' } from '@vue/shared'
import { import {
currentInstance, currentInstance,
@ -335,12 +337,12 @@ function traverse(value: unknown, seen: Set<unknown> = new Set()) {
for (let i = 0; i < value.length; i++) { for (let i = 0; i < value.length; i++) {
traverse(value[i], seen) traverse(value[i], seen)
} }
} else if (value instanceof Map) { } else if (isMap(value)) {
value.forEach((v, key) => { value.forEach((_, key) => {
// to register mutation dep for existing keys // to register mutation dep for existing keys
traverse(value.get(key), seen) traverse(value.get(key), seen)
}) })
} else if (value instanceof Set) { } else if (isSet(value)) {
value.forEach(v => { value.forEach(v => {
traverse(v, seen) traverse(v, seen)
}) })

View File

@ -58,9 +58,11 @@ export const hasOwn = (
): key is keyof typeof val => hasOwnProperty.call(val, key) ): key is keyof typeof val => hasOwnProperty.call(val, key)
export const isArray = Array.isArray export const isArray = Array.isArray
export const isSet = (val: any): boolean => { export const isMap = (val: unknown): val is Map<any, any> =>
return toRawType(val) === 'Set' toTypeString(val) === '[object Map]'
} export const isSet = (val: unknown): val is Set<any> =>
toTypeString(val) === '[object Set]'
export const isDate = (val: unknown): val is Date => val instanceof Date export const isDate = (val: unknown): val is Date => val instanceof Date
export const isFunction = (val: unknown): val is Function => export const isFunction = (val: unknown): val is Function =>
typeof val === 'function' typeof val === 'function'

View File

@ -1,4 +1,4 @@
import { isArray, isObject, isPlainObject } from './index' import { isArray, isMap, isObject, isPlainObject, isSet } from './index'
/** /**
* For converting {{ interpolation }} values to displayed strings. * For converting {{ interpolation }} values to displayed strings.
@ -13,14 +13,14 @@ export const toDisplayString = (val: unknown): string => {
} }
const replacer = (_key: string, val: any) => { const replacer = (_key: string, val: any) => {
if (val instanceof Map) { if (isMap(val)) {
return { return {
[`Map(${val.size})`]: [...val.entries()].reduce((entries, [key, val]) => { [`Map(${val.size})`]: [...val.entries()].reduce((entries, [key, val]) => {
;(entries as any)[`${key} =>`] = val ;(entries as any)[`${key} =>`] = val
return entries return entries
}, {}) }, {})
} }
} else if (val instanceof Set) { } else if (isSet(val)) {
return { return {
[`Set(${val.size})`]: [...val.values()] [`Set(${val.size})`]: [...val.values()]
} }