Procházet zdrojové kódy

fix(reactivity): fix reduce on reactive arrays to preserve reactivity (#12737)

close #12735
inottn před 3 měsíci
rodič
revize
16ef165415

+ 35 - 1
packages/reactivity/__tests__/reactiveArray.spec.ts

@@ -627,7 +627,7 @@ describe('reactivity/reactive/Array', () => {
       expect(left.value).toBe(shallow[0])
       expect(right.value).toBe(shallow[0])
 
-      const deep = reactive([{ val: 1 }, { val: 2 }])
+      let deep = reactive([{ val: 1 }, { val: 2 }])
       left = computed(() => deep.reduce((acc, x) => acc + x.val, '0'))
       right = computed(() => deep.reduceRight((acc, x) => acc + x.val, '3'))
       expect(left.value).toBe('012')
@@ -636,6 +636,40 @@ describe('reactivity/reactive/Array', () => {
       deep[1].val = 23
       expect(left.value).toBe('0123')
       expect(right.value).toBe('3231')
+
+      deep = reactive([{ val: 1 }, { val: 2 }])
+      const maxBy = (prev: any, cur: any) => {
+        expect(isReactive(prev)).toBe(true)
+        expect(isReactive(cur)).toBe(true)
+        return prev.val > cur.val ? prev : cur
+      }
+      left = computed(() => deep.reduce(maxBy))
+      right = computed(() => deep.reduceRight(maxBy))
+      expect(left.value).toMatchObject({ val: 2 })
+      expect(right.value).toMatchObject({ val: 2 })
+
+      deep[0].val = 23
+      expect(left.value).toMatchObject({ val: 23 })
+      expect(right.value).toMatchObject({ val: 23 })
+
+      deep[1].val = 24
+      expect(left.value).toMatchObject({ val: 24 })
+      expect(right.value).toMatchObject({ val: 24 })
+    })
+
+    test('reduce left and right with single deep reactive element and no initial value', () => {
+      const deep = reactive([{ val: 1 }])
+      const left = computed(() => deep.reduce(prev => prev))
+      const right = computed(() => deep.reduceRight(prev => prev))
+
+      expect(isReactive(left.value)).toBe(true)
+      expect(isReactive(right.value)).toBe(true)
+      expect(left.value).toMatchObject({ val: 1 })
+      expect(right.value).toMatchObject({ val: 1 })
+
+      deep[0].val = 2
+      expect(left.value).toMatchObject({ val: 2 })
+      expect(right.value).toMatchObject({ val: 2 })
     })
 
     test('some', () => {

+ 10 - 2
packages/reactivity/src/arrayInstrumentations.ts

@@ -313,10 +313,17 @@ function reduce(
   args: unknown[],
 ) {
   const arr = shallowReadArray(self)
+  const needsWrap = arr !== self && !isShallow(self)
   let wrappedFn = fn
+  let wrapInitialAccumulator = false
   if (arr !== self) {
-    if (!isShallow(self)) {
+    if (needsWrap) {
+      wrapInitialAccumulator = args.length === 0
       wrappedFn = function (this: unknown, acc, item, index) {
+        if (wrapInitialAccumulator) {
+          wrapInitialAccumulator = false
+          acc = toWrapped(self, acc)
+        }
         return fn.call(this, acc, toWrapped(self, item), index, self)
       }
     } else if (fn.length > 3) {
@@ -325,7 +332,8 @@ function reduce(
       }
     }
   }
-  return (arr[method] as any)(wrappedFn, ...args)
+  const result = (arr[method] as any)(wrappedFn, ...args)
+  return wrapInitialAccumulator ? toWrapped(self, result) : result
 }
 
 // instrument identity-sensitive methods to account for reactive proxies