fix(runtime-core): respect immutability for readonly reactive arrays in `v-for` (#13091)
ci / test (push) Has been cancelled Details
ci / continuous-release (push) Has been cancelled Details
size data / upload (push) Has been cancelled Details

close #13087
This commit is contained in:
Tycho 2025-05-02 17:55:24 +08:00 committed by GitHub
parent 9196222ae1
commit 3f27c58ffb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 43 additions and 2 deletions

View File

@ -1,4 +1,10 @@
import { isReactive, reactive, shallowReactive } from '../../src/index'
import {
effect,
isReactive,
reactive,
readonly,
shallowReactive,
} from '../../src/index'
import { renderList } from '../../src/helpers/renderList'
describe('renderList', () => {
@ -65,4 +71,31 @@ describe('renderList', () => {
const shallowReactiveArray = shallowReactive([{ foo: 1 }])
expect(renderList(shallowReactiveArray, isReactive)).toEqual([false])
})
it('should not allow mutation', () => {
const arr = readonly(reactive([{ foo: 1 }]))
expect(
renderList(arr, item => {
;(item as any).foo = 0
return item.foo
}),
).toEqual([1])
expect(
`Set operation on key "foo" failed: target is readonly.`,
).toHaveBeenWarned()
})
it('should trigger effect for deep mutations in readonly reactive arrays', () => {
const arr = reactive([{ foo: 1 }])
const readonlyArr = readonly(arr)
let dummy
effect(() => {
dummy = renderList(readonlyArr, item => item.foo)
})
expect(dummy).toEqual([1])
arr[0].foo = 2
expect(dummy).toEqual([2])
})
})

View File

@ -1,9 +1,11 @@
import type { VNode, VNodeChild } from '../vnode'
import {
isReactive,
isReadonly,
isShallow,
shallowReadArray,
toReactive,
toReadonly,
} from '@vue/reactivity'
import { isArray, isObject, isString } from '@vue/shared'
import { warn } from '../warning'
@ -69,14 +71,20 @@ export function renderList(
if (sourceIsArray || isString(source)) {
const sourceIsReactiveArray = sourceIsArray && isReactive(source)
let needsWrap = false
let isReadonlySource = false
if (sourceIsReactiveArray) {
needsWrap = !isShallow(source)
isReadonlySource = isReadonly(source)
source = shallowReadArray(source)
}
ret = new Array(source.length)
for (let i = 0, l = source.length; i < l; i++) {
ret[i] = renderItem(
needsWrap ? toReactive(source[i]) : source[i],
needsWrap
? isReadonlySource
? toReadonly(toReactive(source[i]))
: toReactive(source[i])
: source[i],
i,
undefined,
cached && cached[i],