Przeglądaj źródła

fix(runtime-core): respect immutability for readonly reactive arrays in `v-for` (#13091)

close #13087
Tycho 11 miesięcy temu
rodzic
commit
3f27c58ffb

+ 34 - 1
packages/runtime-core/__tests__/helpers/renderList.spec.ts

@@ -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])
+  })
 })

+ 9 - 1
packages/runtime-core/src/helpers/renderList.ts

@@ -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],