perf(reactivity): add existing index or non-integer prop on Array should not trigger length dependency (#1969)

This commit is contained in:
xxgjzftd 2020-08-26 23:28:58 +08:00 committed by GitHub
parent 6df0e738cb
commit d5c4f6ed4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 42 additions and 10 deletions

View File

@ -99,6 +99,33 @@ describe('reactivity/reactive/Array', () => {
expect(fn).toHaveBeenCalledTimes(1) expect(fn).toHaveBeenCalledTimes(1)
}) })
test('add existing index on Array should not trigger length dependency', () => {
const array = new Array(3)
const observed = reactive(array)
const fn = jest.fn()
effect(() => {
fn(observed.length)
})
expect(fn).toHaveBeenCalledTimes(1)
observed[1] = 1
expect(fn).toHaveBeenCalledTimes(1)
})
test('add non-integer prop on Array should not trigger length dependency', () => {
const array = new Array(3)
const observed = reactive(array)
const fn = jest.fn()
effect(() => {
fn(observed.length)
})
expect(fn).toHaveBeenCalledTimes(1)
// @ts-ignore
observed.x = 'x'
expect(fn).toHaveBeenCalledTimes(1)
observed[-1] = 'x'
expect(fn).toHaveBeenCalledTimes(1)
})
describe('Array methods w/ refs', () => { describe('Array methods w/ refs', () => {
let original: any[] let original: any[]
beforeEach(() => { beforeEach(() => {

View File

@ -15,6 +15,7 @@ import {
isSymbol, isSymbol,
hasChanged, hasChanged,
isArray, isArray,
isIntegerKey,
extend extend
} from '@vue/shared' } from '@vue/shared'
import { isRef } from './ref' import { isRef } from './ref'
@ -87,10 +88,7 @@ function createGetter(isReadonly = false, shallow = false) {
if (isRef(res)) { if (isRef(res)) {
// ref unwrapping - does not apply for Array + integer key. // ref unwrapping - does not apply for Array + integer key.
const shouldUnwrap = const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
!targetIsArray ||
keyIsSymbol ||
'' + parseInt(key as string, 10) !== key
return shouldUnwrap ? res.value : res return shouldUnwrap ? res.value : res
} }
@ -126,7 +124,10 @@ function createSetter(shallow = false) {
// in shallow mode, objects are set as-is regardless of reactive or not // in shallow mode, objects are set as-is regardless of reactive or not
} }
const hadKey = hasOwn(target, key) const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
const result = Reflect.set(target, key, value, receiver) const result = Reflect.set(target, key, value, receiver)
// don't trigger if target is something up in the prototype chain of original // don't trigger if target is something up in the prototype chain of original
if (target === toRaw(receiver)) { if (target === toRaw(receiver)) {

View File

@ -1,5 +1,5 @@
import { TrackOpTypes, TriggerOpTypes } from './operations' import { TrackOpTypes, TriggerOpTypes } from './operations'
import { EMPTY_OBJ, isArray } from '@vue/shared' import { EMPTY_OBJ, isArray, isIntegerKey } 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
@ -202,16 +202,17 @@ export function trigger(
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 isAddOrDelete = const shouldTriggerIteration =
type === TriggerOpTypes.ADD || (type === TriggerOpTypes.ADD &&
(!isArray(target) || isIntegerKey(key))) ||
(type === TriggerOpTypes.DELETE && !isArray(target)) (type === TriggerOpTypes.DELETE && !isArray(target))
if ( if (
isAddOrDelete || shouldTriggerIteration ||
(type === TriggerOpTypes.SET && target instanceof Map) (type === TriggerOpTypes.SET && target instanceof Map)
) { ) {
add(depsMap.get(isArray(target) ? 'length' : ITERATE_KEY)) add(depsMap.get(isArray(target) ? 'length' : ITERATE_KEY))
} }
if (isAddOrDelete && target instanceof Map) { if (shouldTriggerIteration && target instanceof Map) {
add(depsMap.get(MAP_KEY_ITERATE_KEY)) add(depsMap.get(MAP_KEY_ITERATE_KEY))
} }
} }

View File

@ -81,6 +81,9 @@ export const toRawType = (value: unknown): string => {
export const isPlainObject = (val: unknown): val is object => export const isPlainObject = (val: unknown): val is object =>
toTypeString(val) === '[object Object]' toTypeString(val) === '[object Object]'
export const isIntegerKey = (key: unknown) =>
isString(key) && key[0] !== '-' && '' + parseInt(key, 10) === key
export const isReservedProp = /*#__PURE__*/ makeMap( export const isReservedProp = /*#__PURE__*/ makeMap(
'key,ref,' + 'key,ref,' +
'onVnodeBeforeMount,onVnodeMounted,' + 'onVnodeBeforeMount,onVnodeMounted,' +