fix(runtime-core): v-model listeners that already exists on the component should not be merged (#2011)
fix #1989
This commit is contained in:
parent
aa757e8e6d
commit
63f1f18064
@ -594,4 +594,61 @@ describe('attribute fallthrough', () => {
|
|||||||
button.dispatchEvent(new CustomEvent('click'))
|
button.dispatchEvent(new CustomEvent('click'))
|
||||||
expect(click).toHaveBeenCalled()
|
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')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -17,6 +17,7 @@ import { handleError, ErrorCodes } from './errorHandling'
|
|||||||
import { PatchFlags, ShapeFlags, isOn, isModelListener } from '@vue/shared'
|
import { PatchFlags, ShapeFlags, isOn, isModelListener } from '@vue/shared'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import { isHmrUpdating } from './hmr'
|
import { isHmrUpdating } from './hmr'
|
||||||
|
import { NormalizedProps } from './componentProps'
|
||||||
|
|
||||||
// mark the current rendering instance for asset resolution (e.g.
|
// mark the current rendering instance for asset resolution (e.g.
|
||||||
// resolveComponent, resolveDirective) during render
|
// resolveComponent, resolveDirective) during render
|
||||||
@ -46,6 +47,7 @@ export function renderComponentRoot(
|
|||||||
proxy,
|
proxy,
|
||||||
withProxy,
|
withProxy,
|
||||||
props,
|
props,
|
||||||
|
propsOptions: [propsOptions],
|
||||||
slots,
|
slots,
|
||||||
attrs,
|
attrs,
|
||||||
emit,
|
emit,
|
||||||
@ -125,11 +127,15 @@ export function renderComponentRoot(
|
|||||||
shapeFlag & ShapeFlags.ELEMENT ||
|
shapeFlag & ShapeFlags.ELEMENT ||
|
||||||
shapeFlag & ShapeFlags.COMPONENT
|
shapeFlag & ShapeFlags.COMPONENT
|
||||||
) {
|
) {
|
||||||
if (shapeFlag & ShapeFlags.ELEMENT && keys.some(isModelListener)) {
|
if (propsOptions && keys.some(isModelListener)) {
|
||||||
// #1643, #1543
|
// If a v-model listener (onUpdate:xxx) has a corresponding declared
|
||||||
// component v-model listeners should only fallthrough for component
|
// prop, it indicates this component expects to handle v-model and
|
||||||
// HOCs
|
// it should not fallthrough.
|
||||||
fallthroughAttrs = filterModelListeners(fallthroughAttrs)
|
// related: #1543, #1643, #1989
|
||||||
|
fallthroughAttrs = filterModelListeners(
|
||||||
|
fallthroughAttrs,
|
||||||
|
propsOptions
|
||||||
|
)
|
||||||
}
|
}
|
||||||
root = cloneVNode(root, fallthroughAttrs)
|
root = cloneVNode(root, fallthroughAttrs)
|
||||||
} else if (__DEV__ && !accessedAttrs && root.type !== Comment) {
|
} else if (__DEV__ && !accessedAttrs && root.type !== Comment) {
|
||||||
@ -251,10 +257,10 @@ const getFunctionalFallthrough = (attrs: Data): Data | undefined => {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
const filterModelListeners = (attrs: Data): Data => {
|
const filterModelListeners = (attrs: Data, props: NormalizedProps): Data => {
|
||||||
const res: Data = {}
|
const res: Data = {}
|
||||||
for (const key in attrs) {
|
for (const key in attrs) {
|
||||||
if (!isModelListener(key)) {
|
if (!isModelListener(key) || !(key.slice(9) in props)) {
|
||||||
res[key] = attrs[key]
|
res[key] = attrs[key]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user