mirror of https://github.com/vuejs/core.git
fix(runtime-vapor): respect immutability for readonly reactive arrays in `v-for` (#13187)
This commit is contained in:
parent
a0c42ffbbc
commit
7d84010c0f
|
@ -4,7 +4,14 @@ import {
|
||||||
getRestElement,
|
getRestElement,
|
||||||
renderEffect,
|
renderEffect,
|
||||||
} from '../src'
|
} from '../src'
|
||||||
import { nextTick, ref, shallowRef, triggerRef } from '@vue/runtime-dom'
|
import {
|
||||||
|
nextTick,
|
||||||
|
reactive,
|
||||||
|
readonly,
|
||||||
|
ref,
|
||||||
|
shallowRef,
|
||||||
|
triggerRef,
|
||||||
|
} from '@vue/runtime-dom'
|
||||||
import { makeRender } from './_utils'
|
import { makeRender } from './_utils'
|
||||||
|
|
||||||
const define = makeRender()
|
const define = makeRender()
|
||||||
|
@ -674,4 +681,57 @@ describe('createFor', () => {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expectCalledTimesToBe('Clear rows', 1, 0, 0, 0)
|
expectCalledTimesToBe('Clear rows', 1, 0, 0, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('readonly source', () => {
|
||||||
|
test('should not allow mutation', () => {
|
||||||
|
const arr = readonly(reactive([{ foo: 1 }]))
|
||||||
|
|
||||||
|
const { host } = define(() => {
|
||||||
|
const n1 = createFor(
|
||||||
|
() => arr,
|
||||||
|
(item, key, index) => {
|
||||||
|
const span = document.createElement('li')
|
||||||
|
renderEffect(() => {
|
||||||
|
item.value.foo = 0
|
||||||
|
span.innerHTML = `${item.value.foo}`
|
||||||
|
})
|
||||||
|
return span
|
||||||
|
},
|
||||||
|
idx => idx,
|
||||||
|
)
|
||||||
|
return n1
|
||||||
|
}).render()
|
||||||
|
|
||||||
|
expect(host.innerHTML).toBe('<li>1</li><!--for-->')
|
||||||
|
expect(
|
||||||
|
`Set operation on key "foo" failed: target is readonly.`,
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should trigger effect for deep mutations', async () => {
|
||||||
|
const arr = reactive([{ foo: 1 }])
|
||||||
|
const readonlyArr = readonly(arr)
|
||||||
|
|
||||||
|
const { host } = define(() => {
|
||||||
|
const n1 = createFor(
|
||||||
|
() => readonlyArr,
|
||||||
|
(item, key, index) => {
|
||||||
|
const span = document.createElement('li')
|
||||||
|
renderEffect(() => {
|
||||||
|
span.innerHTML = `${item.value.foo}`
|
||||||
|
})
|
||||||
|
return span
|
||||||
|
},
|
||||||
|
idx => idx,
|
||||||
|
)
|
||||||
|
return n1
|
||||||
|
}).render()
|
||||||
|
|
||||||
|
expect(host.innerHTML).toBe('<li>1</li><!--for-->')
|
||||||
|
|
||||||
|
arr[0].foo = 2
|
||||||
|
await nextTick()
|
||||||
|
expect(host.innerHTML).toBe('<li>2</li><!--for-->')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,12 +2,14 @@ import {
|
||||||
EffectScope,
|
EffectScope,
|
||||||
type ShallowRef,
|
type ShallowRef,
|
||||||
isReactive,
|
isReactive,
|
||||||
|
isReadonly,
|
||||||
isShallow,
|
isShallow,
|
||||||
pauseTracking,
|
pauseTracking,
|
||||||
resetTracking,
|
resetTracking,
|
||||||
shallowReadArray,
|
shallowReadArray,
|
||||||
shallowRef,
|
shallowRef,
|
||||||
toReactive,
|
toReactive,
|
||||||
|
toReadonly,
|
||||||
} from '@vue/reactivity'
|
} from '@vue/reactivity'
|
||||||
import { getSequence, isArray, isObject, isString } from '@vue/shared'
|
import { getSequence, isArray, isObject, isString } from '@vue/shared'
|
||||||
import { createComment, createTextNode } from './dom/node'
|
import { createComment, createTextNode } from './dom/node'
|
||||||
|
@ -59,6 +61,7 @@ type Source = any[] | Record<any, any> | number | Set<any> | Map<any, any>
|
||||||
type ResolvedSource = {
|
type ResolvedSource = {
|
||||||
values: any[]
|
values: any[]
|
||||||
needsWrap: boolean
|
needsWrap: boolean
|
||||||
|
isReadonlySource: boolean
|
||||||
keys?: string[]
|
keys?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -393,11 +396,13 @@ export function createForSlots(
|
||||||
function normalizeSource(source: any): ResolvedSource {
|
function normalizeSource(source: any): ResolvedSource {
|
||||||
let values = source
|
let values = source
|
||||||
let needsWrap = false
|
let needsWrap = false
|
||||||
|
let isReadonlySource = false
|
||||||
let keys
|
let keys
|
||||||
if (isArray(source)) {
|
if (isArray(source)) {
|
||||||
if (isReactive(source)) {
|
if (isReactive(source)) {
|
||||||
needsWrap = !isShallow(source)
|
needsWrap = !isShallow(source)
|
||||||
values = shallowReadArray(source)
|
values = shallowReadArray(source)
|
||||||
|
isReadonlySource = isReadonly(source)
|
||||||
}
|
}
|
||||||
} else if (isString(source)) {
|
} else if (isString(source)) {
|
||||||
values = source.split('')
|
values = source.split('')
|
||||||
|
@ -418,14 +423,23 @@ function normalizeSource(source: any): ResolvedSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { values, needsWrap, keys }
|
return {
|
||||||
|
values,
|
||||||
|
needsWrap,
|
||||||
|
isReadonlySource,
|
||||||
|
keys,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getItem(
|
function getItem(
|
||||||
{ keys, values, needsWrap }: ResolvedSource,
|
{ keys, values, needsWrap, isReadonlySource }: ResolvedSource,
|
||||||
idx: number,
|
idx: number,
|
||||||
): [item: any, key: any, index?: number] {
|
): [item: any, key: any, index?: number] {
|
||||||
const value = needsWrap ? toReactive(values[idx]) : values[idx]
|
const value = needsWrap
|
||||||
|
? isReadonlySource
|
||||||
|
? toReadonly(toReactive(values[idx]))
|
||||||
|
: toReactive(values[idx])
|
||||||
|
: values[idx]
|
||||||
if (keys) {
|
if (keys) {
|
||||||
return [value, keys[idx], idx]
|
return [value, keys[idx], idx]
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue