]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(runtime-core): v-model listeners that already exists on the component should...
authorHcySunYang <HcySunYang@outlook.com>
Wed, 2 Sep 2020 02:24:20 +0000 (10:24 +0800)
committerGitHub <noreply@github.com>
Wed, 2 Sep 2020 02:24:20 +0000 (22:24 -0400)
fix #1989

packages/runtime-core/__tests__/rendererAttrsFallthrough.spec.ts
packages/runtime-core/src/componentRenderUtils.ts

index e2031eb66bb8d7e91c2c757f52ada1df7960d1d0..07348016fed816d1b01e226bc727d253618faa40 100644 (file)
@@ -594,4 +594,61 @@ describe('attribute fallthrough', () => {
     button.dispatchEvent(new CustomEvent('click'))
     expect(click).toHaveBeenCalled()
   })
+
+  // #1989
+  it('should not fallthrough v-model listeners with corresponding declared prop', () => {
+    let textFoo = ''
+    let textBar = ''
+    const click = jest.fn()
+
+    const App = defineComponent({
+      setup() {
+        return () =>
+          h(Child, {
+            modelValue: textFoo,
+            'onUpdate:modelValue': (val: string) => {
+              textFoo = val
+            }
+          })
+      }
+    })
+
+    const Child = defineComponent({
+      props: ['modelValue'],
+      setup(_props, { emit }) {
+        return () =>
+          h(GrandChild, {
+            modelValue: textBar,
+            'onUpdate:modelValue': (val: string) => {
+              textBar = val
+              emit('update:modelValue', 'from Child')
+            }
+          })
+      }
+    })
+
+    const GrandChild = defineComponent({
+      props: ['modelValue'],
+      setup(_props, { emit }) {
+        return () =>
+          h('button', {
+            onClick() {
+              click()
+              emit('update:modelValue', 'from GrandChild')
+            }
+          })
+      }
+    })
+
+    const root = document.createElement('div')
+    document.body.appendChild(root)
+    render(h(App), root)
+
+    const node = root.children[0] as HTMLElement
+
+    node.dispatchEvent(new CustomEvent('click'))
+    expect(click).toHaveBeenCalled()
+    expect(textBar).toBe('from GrandChild')
+    expect(textFoo).toBe('from Child')
+  })
 })
index 9c7ff21a67a5d55a6d184359b2f4c5a7193b4aaf..9fa72c9dd511304e8ef93cddbb37facbb4661759 100644 (file)
@@ -17,6 +17,7 @@ import { handleError, ErrorCodes } from './errorHandling'
 import { PatchFlags, ShapeFlags, isOn, isModelListener } from '@vue/shared'
 import { warn } from './warning'
 import { isHmrUpdating } from './hmr'
+import { NormalizedProps } from './componentProps'
 
 // mark the current rendering instance for asset resolution (e.g.
 // resolveComponent, resolveDirective) during render
@@ -46,6 +47,7 @@ export function renderComponentRoot(
     proxy,
     withProxy,
     props,
+    propsOptions: [propsOptions],
     slots,
     attrs,
     emit,
@@ -125,11 +127,15 @@ export function renderComponentRoot(
           shapeFlag & ShapeFlags.ELEMENT ||
           shapeFlag & ShapeFlags.COMPONENT
         ) {
-          if (shapeFlag & ShapeFlags.ELEMENT && keys.some(isModelListener)) {
-            // #1643, #1543
-            // component v-model listeners should only fallthrough for component
-            // HOCs
-            fallthroughAttrs = filterModelListeners(fallthroughAttrs)
+          if (propsOptions && keys.some(isModelListener)) {
+            // If a v-model listener (onUpdate:xxx) has a corresponding declared
+            // prop, it indicates this component expects to handle v-model and
+            // it should not fallthrough.
+            // related: #1543, #1643, #1989
+            fallthroughAttrs = filterModelListeners(
+              fallthroughAttrs,
+              propsOptions
+            )
           }
           root = cloneVNode(root, fallthroughAttrs)
         } else if (__DEV__ && !accessedAttrs && root.type !== Comment) {
@@ -251,10 +257,10 @@ const getFunctionalFallthrough = (attrs: Data): Data | undefined => {
   return res
 }
 
-const filterModelListeners = (attrs: Data): Data => {
+const filterModelListeners = (attrs: Data, props: NormalizedProps): Data => {
   const res: Data = {}
   for (const key in attrs) {
-    if (!isModelListener(key)) {
+    if (!isModelListener(key) || !(key.slice(9) in props)) {
       res[key] = attrs[key]
     }
   }