]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(defineModel): support local mutation when only prop but no listener is passed
authorEvan You <yyx990803@gmail.com>
Sat, 30 Dec 2023 00:57:55 +0000 (08:57 +0800)
committerEvan You <yyx990803@gmail.com>
Sat, 30 Dec 2023 00:57:55 +0000 (08:57 +0800)
packages/runtime-core/__tests__/apiSetupHelpers.spec.ts
packages/runtime-core/src/apiSetupHelpers.ts

index 0528a14577a41ed1cfeff7a62e4ccd9408ce665d..b50da90d4266816df9b8d4a98acefadbd96b026a 100644 (file)
@@ -279,6 +279,41 @@ describe('SFC <script setup> helpers', () => {
       expect(serializeInner(root)).toBe('bar')
     })
 
+    test('without parent listener (local mutation)', async () => {
+      let foo: any
+      const update = () => {
+        foo.value = 'bar'
+      }
+
+      const compRender = vi.fn()
+      const Comp = defineComponent({
+        props: ['foo'],
+        emits: ['update:foo'],
+        setup(props) {
+          foo = useModel(props, 'foo')
+          return () => {
+            compRender()
+            return foo.value
+          }
+        },
+      })
+
+      const root = nodeOps.createElement('div')
+      // provide initial value
+      render(h(Comp, { foo: 'initial' }), root)
+      expect(compRender).toBeCalledTimes(1)
+      expect(serializeInner(root)).toBe('initial')
+
+      expect(foo.value).toBe('initial')
+      update()
+      // when parent didn't provide value, local mutation is enabled
+      expect(foo.value).toBe('bar')
+
+      await nextTick()
+      expect(compRender).toBeCalledTimes(2)
+      expect(serializeInner(root)).toBe('bar')
+    })
+
     test('default value', async () => {
       let count: any
       const inc = () => {
index dfa1635ad4f5799073908b4fc7954f109655f2f8..ce6050d4b9fb1164c1bb109d5bbd64c1a53a0392 100644 (file)
@@ -3,6 +3,7 @@ import {
   type LooseRequired,
   type Prettify,
   type UnionToIntersection,
+  camelize,
   extend,
   hasChanged,
   isArray,
@@ -380,6 +381,8 @@ export function useModel(
     return ref() as any
   }
 
+  const camelizedName = camelize(name)
+
   const res = customRef((track, trigger) => {
     let localValue: any
     watchSyncEffect(() => {
@@ -396,7 +399,16 @@ export function useModel(
       },
       set(value) {
         const rawProps = i.vnode!.props
-        if (!(rawProps && name in rawProps) && hasChanged(value, localValue)) {
+        if (
+          !(
+            rawProps &&
+            // check if parent has passed v-model
+            (name in rawProps || camelizedName in rawProps) &&
+            (`onUpdate:${name}` in rawProps ||
+              `onUpdate:${camelizedName}` in rawProps)
+          ) &&
+          hasChanged(value, localValue)
+        ) {
           localValue = value
           trigger()
         }