]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(defineModel): ensure trigger effect when prop changed (#9841)
authoredison <daiwei521@126.com>
Sat, 16 Dec 2023 04:15:30 +0000 (12:15 +0800)
committerGitHub <noreply@github.com>
Sat, 16 Dec 2023 04:15:30 +0000 (12:15 +0800)
close #9838

packages/runtime-core/__tests__/apiSetupHelpers.spec.ts
packages/runtime-core/src/apiSetupHelpers.ts

index aceab13650e9cb069644e461e09fbe867478b635..846806e8f42a85e9a4c0fcb13c4c702f995d7763 100644 (file)
@@ -16,7 +16,13 @@ import {
   nextTick,
   ref,
   Ref,
-  watch
+  watch,
+  openBlock,
+  createVNode,
+  createElementVNode,
+  createBlock,
+  createElementBlock,
+  Fragment
 } from '@vue/runtime-test'
 import {
   defineEmits,
@@ -429,6 +435,84 @@ describe('SFC <script setup> helpers', () => {
       await nextTick()
       expect(serializeInner(root)).toBe('2')
     })
+
+    // #9838
+    test('pass modelValue to slot (optimized mode) ', async () => {
+      let foo: any
+      const update = () => {
+        foo.value = 'bar'
+      }
+
+      const Comp = {
+        render(this: any) {
+          return this.$slots.default()
+        }
+      }
+
+      const childRender = vi.fn()
+      const slotRender = vi.fn()
+      const Child = defineComponent({
+        props: ['modelValue'],
+        emits: ['update:modelValue'],
+        setup(props) {
+          foo = useModel(props, 'modelValue')
+          return () => {
+            childRender()
+            return (
+              openBlock(),
+              createElementBlock(Fragment, null, [
+                createVNode(Comp, null, {
+                  default: () => {
+                    slotRender()
+                    return createElementVNode('div', null, foo.value)
+                  },
+                  _: 1 /* STABLE */
+                })
+              ])
+            )
+          }
+        }
+      })
+
+      const msg = ref('')
+      const setValue = vi.fn(v => (msg.value = v))
+      const root = nodeOps.createElement('div')
+      createApp({
+        render() {
+          return (
+            openBlock(),
+            createBlock(
+              Child,
+              {
+                modelValue: msg.value,
+                'onUpdate:modelValue': setValue
+              },
+              null,
+              8 /* PROPS */,
+              ['modelValue']
+            )
+          )
+        }
+      }).mount(root)
+
+      expect(foo.value).toBe('')
+      expect(msg.value).toBe('')
+      expect(setValue).not.toBeCalled()
+      expect(childRender).toBeCalledTimes(1)
+      expect(slotRender).toBeCalledTimes(1)
+      expect(serializeInner(root)).toBe('<div></div>')
+
+      // update from child
+      update()
+
+      await nextTick()
+      expect(msg.value).toBe('bar')
+      expect(foo.value).toBe('bar')
+      expect(setValue).toBeCalledTimes(1)
+      expect(childRender).toBeCalledTimes(2)
+      expect(slotRender).toBeCalledTimes(2)
+      expect(serializeInner(root)).toBe('<div>bar</div>')
+    })
   })
 
   test('createPropsRestProxy', () => {
index 34fdcaee2e82137a36dbcfcbc24d828623891f58..4f8f3853300da37b9e324ae9229f7c3a6c72d6f9 100644 (file)
@@ -364,25 +364,30 @@ export function useModel(props: Record<string, any>, name: string): Ref {
     return ref() as any
   }
 
-  let localValue: any
-  watchSyncEffect(() => {
-    localValue = props[name]
-  })
-
-  return customRef((track, trigger) => ({
-    get() {
-      track()
-      return localValue
-    },
-    set(value) {
-      const rawProps = i.vnode!.props
-      if (!(rawProps && name in rawProps) && hasChanged(value, localValue)) {
-        localValue = value
+  return customRef((track, trigger) => {
+    let localValue: any
+    watchSyncEffect(() => {
+      const propValue = props[name]
+      if (hasChanged(localValue, propValue)) {
+        localValue = propValue
         trigger()
       }
-      i.emit(`update:${name}`, value)
+    })
+    return {
+      get() {
+        track()
+        return localValue
+      },
+      set(value) {
+        const rawProps = i.vnode!.props
+        if (!(rawProps && name in rawProps) && hasChanged(value, localValue)) {
+          localValue = value
+          trigger()
+        }
+        i.emit(`update:${name}`, value)
+      }
     }
-  }))
+  })
 }
 
 function getContext(): SetupContext {