Skip to content

Commit

Permalink
fix(runtime-core): v-model listeners that already exists on the compo…
Browse files Browse the repository at this point in the history
…nent should not be merged (#2011)

fix #1989
  • Loading branch information
HcySunYang authored Sep 2, 2020
1 parent aa757e8 commit 63f1f18
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 7 deletions.
57 changes: 57 additions & 0 deletions packages/runtime-core/__tests__/rendererAttrsFallthrough.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
})
})
20 changes: 13 additions & 7 deletions packages/runtime-core/src/componentRenderUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -46,6 +47,7 @@ export function renderComponentRoot(
proxy,
withProxy,
props,
propsOptions: [propsOptions],
slots,
attrs,
emit,
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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]
}
}
Expand Down

0 comments on commit 63f1f18

Please sign in to comment.