Skip to content

Commit 6fca482

Browse files
committed
fix(reactivity): correctly wrap iterated array items to preserve their readonly status
1 parent 83f6ab6 commit 6fca482

File tree

2 files changed

+58
-9
lines changed

2 files changed

+58
-9
lines changed

packages/reactivity/__tests__/readonly.spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,19 @@ describe('reactivity/readonly', () => {
172172
expect(dummy).toBe(1)
173173
expect(`target is readonly`).toHaveBeenWarnedTimes(2)
174174
})
175+
176+
it('should maintain identity when iterating readonly ref array', () => {
177+
const list = readonly(ref([{}, {}, {}]))
178+
const computedList = computed(() => {
179+
const newList: any[] = []
180+
list.value.forEach(x => newList.push(x))
181+
return newList
182+
})
183+
184+
expect(list.value[0]).toBe(computedList.value[0])
185+
expect(isReadonly(computedList.value[0])).toBe(true)
186+
expect(isReactive(computedList.value[0])).toBe(true)
187+
})
175188
})
176189

177190
const maps = [Map, WeakMap]

packages/reactivity/src/arrayInstrumentations.ts

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
import { TrackOpTypes } from './constants'
22
import { endBatch, pauseTracking, resetTracking, startBatch } from './effect'
3-
import { isProxy, isShallow, toRaw, toReactive } from './reactive'
3+
import {
4+
isProxy,
5+
isReactive,
6+
isReadonly,
7+
isShallow,
8+
toRaw,
9+
toReactive,
10+
toReadonly,
11+
} from './reactive'
412
import { ARRAY_ITERATE_KEY, track } from './dep'
513
import { isArray } from '@vue/shared'
614

@@ -24,11 +32,18 @@ export function shallowReadArray<T>(arr: T[]): T[] {
2432
return arr
2533
}
2634

35+
function toWrapped(target: unknown, item: unknown) {
36+
if (isReadonly(target)) {
37+
return isReactive(target) ? toReadonly(toReactive(item)) : toReadonly(item)
38+
}
39+
return toReactive(item)
40+
}
41+
2742
export const arrayInstrumentations: Record<string | symbol, Function> = <any>{
2843
__proto__: null,
2944

3045
[Symbol.iterator]() {
31-
return iterator(this, Symbol.iterator, toReactive)
46+
return iterator(this, Symbol.iterator, item => toWrapped(this, item))
3247
},
3348

3449
concat(...args: unknown[]) {
@@ -39,7 +54,7 @@ export const arrayInstrumentations: Record<string | symbol, Function> = <any>{
3954

4055
entries() {
4156
return iterator(this, 'entries', (value: [number, unknown]) => {
42-
value[1] = toReactive(value[1])
57+
value[1] = toWrapped(this, value[1])
4358
return value
4459
})
4560
},
@@ -55,14 +70,28 @@ export const arrayInstrumentations: Record<string | symbol, Function> = <any>{
5570
fn: (item: unknown, index: number, array: unknown[]) => unknown,
5671
thisArg?: unknown,
5772
) {
58-
return apply(this, 'filter', fn, thisArg, v => v.map(toReactive), arguments)
73+
return apply(
74+
this,
75+
'filter',
76+
fn,
77+
thisArg,
78+
v => v.map((item: unknown) => toWrapped(this, item)),
79+
arguments,
80+
)
5981
},
6082

6183
find(
6284
fn: (item: unknown, index: number, array: unknown[]) => boolean,
6385
thisArg?: unknown,
6486
) {
65-
return apply(this, 'find', fn, thisArg, toReactive, arguments)
87+
return apply(
88+
this,
89+
'find',
90+
fn,
91+
thisArg,
92+
item => toWrapped(this, item),
93+
arguments,
94+
)
6695
},
6796

6897
findIndex(
@@ -76,7 +105,14 @@ export const arrayInstrumentations: Record<string | symbol, Function> = <any>{
76105
fn: (item: unknown, index: number, array: unknown[]) => boolean,
77106
thisArg?: unknown,
78107
) {
79-
return apply(this, 'findLast', fn, thisArg, toReactive, arguments)
108+
return apply(
109+
this,
110+
'findLast',
111+
fn,
112+
thisArg,
113+
item => toWrapped(this, item),
114+
arguments,
115+
)
80116
},
81117

82118
findLastIndex(
@@ -189,7 +225,7 @@ export const arrayInstrumentations: Record<string | symbol, Function> = <any>{
189225
},
190226

191227
values() {
192-
return iterator(this, 'values', toReactive)
228+
return iterator(this, 'values', item => toWrapped(this, item))
193229
},
194230
}
195231

@@ -257,7 +293,7 @@ function apply(
257293
if (arr !== self) {
258294
if (needsWrap) {
259295
wrappedFn = function (this: unknown, item, index) {
260-
return fn.call(this, toReactive(item), index, self)
296+
return fn.call(this, toWrapped(self, item), index, self)
261297
}
262298
} else if (fn.length > 2) {
263299
wrappedFn = function (this: unknown, item, index) {
@@ -281,7 +317,7 @@ function reduce(
281317
if (arr !== self) {
282318
if (!isShallow(self)) {
283319
wrappedFn = function (this: unknown, acc, item, index) {
284-
return fn.call(this, acc, toReactive(item), index, self)
320+
return fn.call(this, acc, toWrapped(self, item), index, self)
285321
}
286322
} else if (fn.length > 3) {
287323
wrappedFn = function (this: unknown, acc, item, index) {

0 commit comments

Comments
 (0)