]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
perf(reactivity): add existing index or non-integer prop on Array should not trigger...
authorxxgjzftd <xxgjzftd@gmail.com>
Wed, 26 Aug 2020 15:28:58 +0000 (23:28 +0800)
committerGitHub <noreply@github.com>
Wed, 26 Aug 2020 15:28:58 +0000 (11:28 -0400)
packages/reactivity/__tests__/reactiveArray.spec.ts
packages/reactivity/src/baseHandlers.ts
packages/reactivity/src/effect.ts
packages/shared/src/index.ts

index 415e1a5119389378458585ecb636475f99da5930..0ab458d34f18f6635107556ae8a4f099314b339e 100644 (file)
@@ -99,6 +99,33 @@ describe('reactivity/reactive/Array', () => {
     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', () => {
     let original: any[]
     beforeEach(() => {
index ef944a968a67342ea587cb18435f2d0429489ff7..0e86d4439dd55120644607cf214a3dfa33cb2781 100644 (file)
@@ -15,6 +15,7 @@ import {
   isSymbol,
   hasChanged,
   isArray,
+  isIntegerKey,
   extend
 } from '@vue/shared'
 import { isRef } from './ref'
@@ -87,10 +88,7 @@ function createGetter(isReadonly = false, shallow = false) {
 
     if (isRef(res)) {
       // ref unwrapping - does not apply for Array + integer key.
-      const shouldUnwrap =
-        !targetIsArray ||
-        keyIsSymbol ||
-        '' + parseInt(key as string, 10) !== key
+      const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
       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
     }
 
-    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)
     // don't trigger if target is something up in the prototype chain of original
     if (target === toRaw(receiver)) {
index 15988daadfe140d10b6d90dee7d21d1863539264..fb17e575e1f2d18bbeeca4f4ee3b6e9300e20b1d 100644 (file)
@@ -1,5 +1,5 @@
 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.
 // Conceptually, it's easier to think of a dependency as a Dep class
@@ -202,16 +202,17 @@ export function trigger(
       add(depsMap.get(key))
     }
     // also run for iteration key on ADD | DELETE | Map.SET
-    const isAddOrDelete =
-      type === TriggerOpTypes.ADD ||
+    const shouldTriggerIteration =
+      (type === TriggerOpTypes.ADD &&
+        (!isArray(target) || isIntegerKey(key))) ||
       (type === TriggerOpTypes.DELETE && !isArray(target))
     if (
-      isAddOrDelete ||
+      shouldTriggerIteration ||
       (type === TriggerOpTypes.SET && target instanceof Map)
     ) {
       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))
     }
   }
index 8f8bd9df7a9512868e39a81d70522e3f20f98966..69739cae2518ae6fe9b364c6e57495ed081b2164 100644 (file)
@@ -81,6 +81,9 @@ export const toRawType = (value: unknown): string => {
 export const isPlainObject = (val: unknown): val is object =>
   toTypeString(val) === '[object Object]'
 
+export const isIntegerKey = (key: unknown) =>
+  isString(key) && key[0] !== '-' && '' + parseInt(key, 10) === key
+
 export const isReservedProp = /*#__PURE__*/ makeMap(
   'key,ref,' +
     'onVnodeBeforeMount,onVnodeMounted,' +