Skip to content

Commit 7d84010

Browse files
authored
fix(runtime-vapor): respect immutability for readonly reactive arrays in v-for (#13187)
1 parent a0c42ff commit 7d84010

File tree

2 files changed

+78
-4
lines changed

2 files changed

+78
-4
lines changed

packages/runtime-vapor/__tests__/for.spec.ts

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@ import {
44
getRestElement,
55
renderEffect,
66
} from '../src'
7-
import { nextTick, ref, shallowRef, triggerRef } from '@vue/runtime-dom'
7+
import {
8+
nextTick,
9+
reactive,
10+
readonly,
11+
ref,
12+
shallowRef,
13+
triggerRef,
14+
} from '@vue/runtime-dom'
815
import { makeRender } from './_utils'
916

1017
const define = makeRender()
@@ -674,4 +681,57 @@ describe('createFor', () => {
674681
await nextTick()
675682
expectCalledTimesToBe('Clear rows', 1, 0, 0, 0)
676683
})
684+
685+
describe('readonly source', () => {
686+
test('should not allow mutation', () => {
687+
const arr = readonly(reactive([{ foo: 1 }]))
688+
689+
const { host } = define(() => {
690+
const n1 = createFor(
691+
() => arr,
692+
(item, key, index) => {
693+
const span = document.createElement('li')
694+
renderEffect(() => {
695+
item.value.foo = 0
696+
span.innerHTML = `${item.value.foo}`
697+
})
698+
return span
699+
},
700+
idx => idx,
701+
)
702+
return n1
703+
}).render()
704+
705+
expect(host.innerHTML).toBe('<li>1</li><!--for-->')
706+
expect(
707+
`Set operation on key "foo" failed: target is readonly.`,
708+
).toHaveBeenWarned()
709+
})
710+
711+
test('should trigger effect for deep mutations', async () => {
712+
const arr = reactive([{ foo: 1 }])
713+
const readonlyArr = readonly(arr)
714+
715+
const { host } = define(() => {
716+
const n1 = createFor(
717+
() => readonlyArr,
718+
(item, key, index) => {
719+
const span = document.createElement('li')
720+
renderEffect(() => {
721+
span.innerHTML = `${item.value.foo}`
722+
})
723+
return span
724+
},
725+
idx => idx,
726+
)
727+
return n1
728+
}).render()
729+
730+
expect(host.innerHTML).toBe('<li>1</li><!--for-->')
731+
732+
arr[0].foo = 2
733+
await nextTick()
734+
expect(host.innerHTML).toBe('<li>2</li><!--for-->')
735+
})
736+
})
677737
})

packages/runtime-vapor/src/apiCreateFor.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import {
22
EffectScope,
33
type ShallowRef,
44
isReactive,
5+
isReadonly,
56
isShallow,
67
pauseTracking,
78
resetTracking,
89
shallowReadArray,
910
shallowRef,
1011
toReactive,
12+
toReadonly,
1113
} from '@vue/reactivity'
1214
import { getSequence, isArray, isObject, isString } from '@vue/shared'
1315
import { createComment, createTextNode } from './dom/node'
@@ -59,6 +61,7 @@ type Source = any[] | Record<any, any> | number | Set<any> | Map<any, any>
5961
type ResolvedSource = {
6062
values: any[]
6163
needsWrap: boolean
64+
isReadonlySource: boolean
6265
keys?: string[]
6366
}
6467

@@ -393,11 +396,13 @@ export function createForSlots(
393396
function normalizeSource(source: any): ResolvedSource {
394397
let values = source
395398
let needsWrap = false
399+
let isReadonlySource = false
396400
let keys
397401
if (isArray(source)) {
398402
if (isReactive(source)) {
399403
needsWrap = !isShallow(source)
400404
values = shallowReadArray(source)
405+
isReadonlySource = isReadonly(source)
401406
}
402407
} else if (isString(source)) {
403408
values = source.split('')
@@ -418,14 +423,23 @@ function normalizeSource(source: any): ResolvedSource {
418423
}
419424
}
420425
}
421-
return { values, needsWrap, keys }
426+
return {
427+
values,
428+
needsWrap,
429+
isReadonlySource,
430+
keys,
431+
}
422432
}
423433

424434
function getItem(
425-
{ keys, values, needsWrap }: ResolvedSource,
435+
{ keys, values, needsWrap, isReadonlySource }: ResolvedSource,
426436
idx: number,
427437
): [item: any, key: any, index?: number] {
428-
const value = needsWrap ? toReactive(values[idx]) : values[idx]
438+
const value = needsWrap
439+
? isReadonlySource
440+
? toReadonly(toReactive(values[idx]))
441+
: toReactive(values[idx])
442+
: values[idx]
429443
if (keys) {
430444
return [value, keys[idx], idx]
431445
} else {

0 commit comments

Comments
 (0)