Skip to content

Commit 3f27c58

Browse files
authored
fix(runtime-core): respect immutability for readonly reactive arrays in v-for (#13091)
close #13087
1 parent 9196222 commit 3f27c58

File tree

2 files changed

+43
-2
lines changed

2 files changed

+43
-2
lines changed

packages/runtime-core/__tests__/helpers/renderList.spec.ts

+34-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { isReactive, reactive, shallowReactive } from '../../src/index'
1+
import {
2+
effect,
3+
isReactive,
4+
reactive,
5+
readonly,
6+
shallowReactive,
7+
} from '../../src/index'
28
import { renderList } from '../../src/helpers/renderList'
39

410
describe('renderList', () => {
@@ -65,4 +71,31 @@ describe('renderList', () => {
6571
const shallowReactiveArray = shallowReactive([{ foo: 1 }])
6672
expect(renderList(shallowReactiveArray, isReactive)).toEqual([false])
6773
})
74+
75+
it('should not allow mutation', () => {
76+
const arr = readonly(reactive([{ foo: 1 }]))
77+
expect(
78+
renderList(arr, item => {
79+
;(item as any).foo = 0
80+
return item.foo
81+
}),
82+
).toEqual([1])
83+
expect(
84+
`Set operation on key "foo" failed: target is readonly.`,
85+
).toHaveBeenWarned()
86+
})
87+
88+
it('should trigger effect for deep mutations in readonly reactive arrays', () => {
89+
const arr = reactive([{ foo: 1 }])
90+
const readonlyArr = readonly(arr)
91+
92+
let dummy
93+
effect(() => {
94+
dummy = renderList(readonlyArr, item => item.foo)
95+
})
96+
expect(dummy).toEqual([1])
97+
98+
arr[0].foo = 2
99+
expect(dummy).toEqual([2])
100+
})
68101
})

packages/runtime-core/src/helpers/renderList.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import type { VNode, VNodeChild } from '../vnode'
22
import {
33
isReactive,
4+
isReadonly,
45
isShallow,
56
shallowReadArray,
67
toReactive,
8+
toReadonly,
79
} from '@vue/reactivity'
810
import { isArray, isObject, isString } from '@vue/shared'
911
import { warn } from '../warning'
@@ -69,14 +71,20 @@ export function renderList(
6971
if (sourceIsArray || isString(source)) {
7072
const sourceIsReactiveArray = sourceIsArray && isReactive(source)
7173
let needsWrap = false
74+
let isReadonlySource = false
7275
if (sourceIsReactiveArray) {
7376
needsWrap = !isShallow(source)
77+
isReadonlySource = isReadonly(source)
7478
source = shallowReadArray(source)
7579
}
7680
ret = new Array(source.length)
7781
for (let i = 0, l = source.length; i < l; i++) {
7882
ret[i] = renderItem(
79-
needsWrap ? toReactive(source[i]) : source[i],
83+
needsWrap
84+
? isReadonlySource
85+
? toReadonly(toReactive(source[i]))
86+
: toReactive(source[i])
87+
: source[i],
8088
i,
8189
undefined,
8290
cached && cached[i],

0 commit comments

Comments
 (0)