]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(reactivity): correctly wrap iterated array items to preserve their readonly statu...
authoredison <daiwei521@126.com>
Mon, 24 Nov 2025 07:20:23 +0000 (15:20 +0800)
committerGitHub <noreply@github.com>
Mon, 24 Nov 2025 07:20:23 +0000 (15:20 +0800)
packages/reactivity/__tests__/readonly.spec.ts
packages/reactivity/src/arrayInstrumentations.ts

index a6f7542ce12f9760faf8399e5de6d5e3900d9f9f..96e688e38e01ba5b9d85bd5d72bc3ccd551fd6bf 100644 (file)
@@ -172,6 +172,19 @@ describe('reactivity/readonly', () => {
       expect(dummy).toBe(1)
       expect(`target is readonly`).toHaveBeenWarnedTimes(2)
     })
+
+    it('should maintain identity when iterating readonly ref array', () => {
+      const list = readonly(ref([{}, {}, {}]))
+      const computedList = computed(() => {
+        const newList: any[] = []
+        list.value.forEach(x => newList.push(x))
+        return newList
+      })
+
+      expect(list.value[0]).toBe(computedList.value[0])
+      expect(isReadonly(computedList.value[0])).toBe(true)
+      expect(isReactive(computedList.value[0])).toBe(true)
+    })
   })
 
   const maps = [Map, WeakMap]
index 981a10d04fc0c570cb2e9180c3c1a176906e6d38..f37b056516aec879b2aed1cd58fc48693b51d092 100644 (file)
@@ -1,6 +1,14 @@
 import { TrackOpTypes } from './constants'
 import { endBatch, pauseTracking, resetTracking, startBatch } from './effect'
-import { isProxy, isShallow, toRaw, toReactive } from './reactive'
+import {
+  isProxy,
+  isReactive,
+  isReadonly,
+  isShallow,
+  toRaw,
+  toReactive,
+  toReadonly,
+} from './reactive'
 import { ARRAY_ITERATE_KEY, track } from './dep'
 import { isArray } from '@vue/shared'
 
@@ -24,11 +32,18 @@ export function shallowReadArray<T>(arr: T[]): T[] {
   return arr
 }
 
+function toWrapped(target: unknown, item: unknown) {
+  if (isReadonly(target)) {
+    return isReactive(target) ? toReadonly(toReactive(item)) : toReadonly(item)
+  }
+  return toReactive(item)
+}
+
 export const arrayInstrumentations: Record<string | symbol, Function> = <any>{
   __proto__: null,
 
   [Symbol.iterator]() {
-    return iterator(this, Symbol.iterator, toReactive)
+    return iterator(this, Symbol.iterator, item => toWrapped(this, item))
   },
 
   concat(...args: unknown[]) {
@@ -39,7 +54,7 @@ export const arrayInstrumentations: Record<string | symbol, Function> = <any>{
 
   entries() {
     return iterator(this, 'entries', (value: [number, unknown]) => {
-      value[1] = toReactive(value[1])
+      value[1] = toWrapped(this, value[1])
       return value
     })
   },
@@ -55,14 +70,28 @@ export const arrayInstrumentations: Record<string | symbol, Function> = <any>{
     fn: (item: unknown, index: number, array: unknown[]) => unknown,
     thisArg?: unknown,
   ) {
-    return apply(this, 'filter', fn, thisArg, v => v.map(toReactive), arguments)
+    return apply(
+      this,
+      'filter',
+      fn,
+      thisArg,
+      v => v.map((item: unknown) => toWrapped(this, item)),
+      arguments,
+    )
   },
 
   find(
     fn: (item: unknown, index: number, array: unknown[]) => boolean,
     thisArg?: unknown,
   ) {
-    return apply(this, 'find', fn, thisArg, toReactive, arguments)
+    return apply(
+      this,
+      'find',
+      fn,
+      thisArg,
+      item => toWrapped(this, item),
+      arguments,
+    )
   },
 
   findIndex(
@@ -76,7 +105,14 @@ export const arrayInstrumentations: Record<string | symbol, Function> = <any>{
     fn: (item: unknown, index: number, array: unknown[]) => boolean,
     thisArg?: unknown,
   ) {
-    return apply(this, 'findLast', fn, thisArg, toReactive, arguments)
+    return apply(
+      this,
+      'findLast',
+      fn,
+      thisArg,
+      item => toWrapped(this, item),
+      arguments,
+    )
   },
 
   findLastIndex(
@@ -189,7 +225,7 @@ export const arrayInstrumentations: Record<string | symbol, Function> = <any>{
   },
 
   values() {
-    return iterator(this, 'values', toReactive)
+    return iterator(this, 'values', item => toWrapped(this, item))
   },
 }
 
@@ -257,7 +293,7 @@ function apply(
   if (arr !== self) {
     if (needsWrap) {
       wrappedFn = function (this: unknown, item, index) {
-        return fn.call(this, toReactive(item), index, self)
+        return fn.call(this, toWrapped(self, item), index, self)
       }
     } else if (fn.length > 2) {
       wrappedFn = function (this: unknown, item, index) {
@@ -281,7 +317,7 @@ function reduce(
   if (arr !== self) {
     if (!isShallow(self)) {
       wrappedFn = function (this: unknown, acc, item, index) {
-        return fn.call(this, acc, toReactive(item), index, self)
+        return fn.call(this, acc, toWrapped(self, item), index, self)
       }
     } else if (fn.length > 3) {
       wrappedFn = function (this: unknown, acc, item, index) {