Skip to content

Commit b188fcc

Browse files
committed
fix(runtime-vapor): respect immutability for readonly reactive arrays in v-for
1 parent 913f05f commit b188fcc

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'
@@ -55,6 +57,7 @@ type Source = any[] | Record<any, any> | number | Set<any> | Map<any, any>
5557
type ResolvedSource = {
5658
values: any[]
5759
needsWrap: boolean
60+
isReadonlySource: boolean
5861
keys?: string[]
5962
}
6063

@@ -387,11 +390,13 @@ export function createForSlots(
387390
function normalizeSource(source: any): ResolvedSource {
388391
let values = source
389392
let needsWrap = false
393+
let isReadonlySource = false
390394
let keys
391395
if (isArray(source)) {
392396
if (isReactive(source)) {
393397
needsWrap = !isShallow(source)
394398
values = shallowReadArray(source)
399+
isReadonlySource = isReadonly(source)
395400
}
396401
} else if (isString(source)) {
397402
values = source.split('')
@@ -412,14 +417,23 @@ function normalizeSource(source: any): ResolvedSource {
412417
}
413418
}
414419
}
415-
return { values, needsWrap, keys }
420+
return {
421+
values,
422+
needsWrap,
423+
isReadonlySource,
424+
keys,
425+
}
416426
}
417427

418428
function getItem(
419-
{ keys, values, needsWrap }: ResolvedSource,
429+
{ keys, values, needsWrap, isReadonlySource }: ResolvedSource,
420430
idx: number,
421431
): [item: any, key: any, index?: number] {
422-
const value = needsWrap ? toReactive(values[idx]) : values[idx]
432+
const value = needsWrap
433+
? isReadonlySource
434+
? toReadonly(toReactive(values[idx]))
435+
: toReactive(values[idx])
436+
: values[idx]
423437
if (keys) {
424438
return [value, keys[idx], idx]
425439
} else {

0 commit comments

Comments
 (0)