Skip to content

Commit d5c4f6e

Browse files
authored
perf(reactivity): add existing index or non-integer prop on Array should not trigger length dependency (#1969)
1 parent 6df0e73 commit d5c4f6e

File tree

4 files changed

+42
-10
lines changed

4 files changed

+42
-10
lines changed

packages/reactivity/__tests__/reactiveArray.spec.ts

+27
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,33 @@ describe('reactivity/reactive/Array', () => {
9999
expect(fn).toHaveBeenCalledTimes(1)
100100
})
101101

102+
test('add existing index on Array should not trigger length dependency', () => {
103+
const array = new Array(3)
104+
const observed = reactive(array)
105+
const fn = jest.fn()
106+
effect(() => {
107+
fn(observed.length)
108+
})
109+
expect(fn).toHaveBeenCalledTimes(1)
110+
observed[1] = 1
111+
expect(fn).toHaveBeenCalledTimes(1)
112+
})
113+
114+
test('add non-integer prop on Array should not trigger length dependency', () => {
115+
const array = new Array(3)
116+
const observed = reactive(array)
117+
const fn = jest.fn()
118+
effect(() => {
119+
fn(observed.length)
120+
})
121+
expect(fn).toHaveBeenCalledTimes(1)
122+
// @ts-ignore
123+
observed.x = 'x'
124+
expect(fn).toHaveBeenCalledTimes(1)
125+
observed[-1] = 'x'
126+
expect(fn).toHaveBeenCalledTimes(1)
127+
})
128+
102129
describe('Array methods w/ refs', () => {
103130
let original: any[]
104131
beforeEach(() => {

packages/reactivity/src/baseHandlers.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
isSymbol,
1616
hasChanged,
1717
isArray,
18+
isIntegerKey,
1819
extend
1920
} from '@vue/shared'
2021
import { isRef } from './ref'
@@ -87,10 +88,7 @@ function createGetter(isReadonly = false, shallow = false) {
8788

8889
if (isRef(res)) {
8990
// ref unwrapping - does not apply for Array + integer key.
90-
const shouldUnwrap =
91-
!targetIsArray ||
92-
keyIsSymbol ||
93-
'' + parseInt(key as string, 10) !== key
91+
const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
9492
return shouldUnwrap ? res.value : res
9593
}
9694

@@ -126,7 +124,10 @@ function createSetter(shallow = false) {
126124
// in shallow mode, objects are set as-is regardless of reactive or not
127125
}
128126

129-
const hadKey = hasOwn(target, key)
127+
const hadKey =
128+
isArray(target) && isIntegerKey(key)
129+
? Number(key) < target.length
130+
: hasOwn(target, key)
130131
const result = Reflect.set(target, key, value, receiver)
131132
// don't trigger if target is something up in the prototype chain of original
132133
if (target === toRaw(receiver)) {

packages/reactivity/src/effect.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { TrackOpTypes, TriggerOpTypes } from './operations'
2-
import { EMPTY_OBJ, isArray } from '@vue/shared'
2+
import { EMPTY_OBJ, isArray, isIntegerKey } from '@vue/shared'
33

44
// The main WeakMap that stores {target -> key -> dep} connections.
55
// Conceptually, it's easier to think of a dependency as a Dep class
@@ -202,16 +202,17 @@ export function trigger(
202202
add(depsMap.get(key))
203203
}
204204
// also run for iteration key on ADD | DELETE | Map.SET
205-
const isAddOrDelete =
206-
type === TriggerOpTypes.ADD ||
205+
const shouldTriggerIteration =
206+
(type === TriggerOpTypes.ADD &&
207+
(!isArray(target) || isIntegerKey(key))) ||
207208
(type === TriggerOpTypes.DELETE && !isArray(target))
208209
if (
209-
isAddOrDelete ||
210+
shouldTriggerIteration ||
210211
(type === TriggerOpTypes.SET && target instanceof Map)
211212
) {
212213
add(depsMap.get(isArray(target) ? 'length' : ITERATE_KEY))
213214
}
214-
if (isAddOrDelete && target instanceof Map) {
215+
if (shouldTriggerIteration && target instanceof Map) {
215216
add(depsMap.get(MAP_KEY_ITERATE_KEY))
216217
}
217218
}

packages/shared/src/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ export const toRawType = (value: unknown): string => {
8181
export const isPlainObject = (val: unknown): val is object =>
8282
toTypeString(val) === '[object Object]'
8383

84+
export const isIntegerKey = (key: unknown) =>
85+
isString(key) && key[0] !== '-' && '' + parseInt(key, 10) === key
86+
8487
export const isReservedProp = /*#__PURE__*/ makeMap(
8588
'key,ref,' +
8689
'onVnodeBeforeMount,onVnodeMounted,' +

0 commit comments

Comments
 (0)