fix(v-model): v-model listeners should not fallthrough to plain element root

fix #1643
This commit is contained in:
Evan You 2020-07-21 14:17:48 -04:00
parent 304830a764
commit c852bf18d7
4 changed files with 71 additions and 53 deletions

View File

@ -14,7 +14,7 @@ import {
isVNode
} from './vnode'
import { handleError, ErrorCodes } from './errorHandling'
import { PatchFlags, ShapeFlags, isOn } from '@vue/shared'
import { PatchFlags, ShapeFlags, isOn, isModelListener } from '@vue/shared'
import { warn } from './warning'
import { isHmrUpdating } from './hmr'
@ -104,7 +104,9 @@ export function renderComponentRoot(
)
: render(props, null as any /* we know it doesn't need it */)
)
fallthroughAttrs = Component.props ? attrs : getFallthroughAttrs(attrs)
fallthroughAttrs = Component.props
? attrs
: getFunctionalFallthrough(attrs)
}
// attr merging
@ -116,15 +118,20 @@ export function renderComponentRoot(
;[root, setRoot] = getChildRoot(result)
}
if (Component.inheritAttrs !== false && fallthroughAttrs) {
const keys = Object.keys(fallthroughAttrs)
const { shapeFlag } = root
if (keys.length) {
if (
Component.inheritAttrs !== false &&
fallthroughAttrs &&
Object.keys(fallthroughAttrs).length
) {
if (
root.shapeFlag & ShapeFlags.ELEMENT ||
root.shapeFlag & ShapeFlags.COMPONENT
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)
}
root = cloneVNode(root, fallthroughAttrs)
} else if (__DEV__ && !accessedAttrs && root.type !== Comment) {
const allAttrs = Object.keys(attrs)
@ -134,7 +141,7 @@ export function renderComponentRoot(
const key = allAttrs[i]
if (isOn(key)) {
// ignore v-model handlers when they fail to fallthrough
if (!key.startsWith('onUpdate:')) {
if (!isModelListener(key)) {
// remove `on`, lowercase first letter to reflect event casing
// accurately
eventAttrs.push(key[2].toLowerCase() + key.slice(3))
@ -163,6 +170,7 @@ export function renderComponentRoot(
}
}
}
}
// inherit scopeId
const scopeId = vnode.scopeId
@ -246,7 +254,7 @@ const getChildRoot = (
return [normalizeVNode(childRoot), setRoot]
}
const getFallthroughAttrs = (attrs: Data): Data | undefined => {
const getFunctionalFallthrough = (attrs: Data): Data | undefined => {
let res: Data | undefined
for (const key in attrs) {
if (key === 'class' || key === 'style' || isOn(key)) {
@ -256,6 +264,16 @@ const getFallthroughAttrs = (attrs: Data): Data | undefined => {
return res
}
const filterModelListeners = (attrs: Data): Data => {
const res: Data = {}
for (const key in attrs) {
if (!isModelListener(key)) {
res[key] = attrs[key]
}
}
return res
}
const isElementRoot = (vnode: VNode) => {
return (
vnode.shapeFlag & ShapeFlags.COMPONENT ||

View File

@ -9,7 +9,8 @@ import {
normalizeStyle,
PatchFlags,
ShapeFlags,
SlotFlags
SlotFlags,
isOn
} from '@vue/shared'
import {
ComponentInternalInstance,
@ -583,8 +584,6 @@ export function normalizeChildren(vnode: VNode, children: unknown) {
vnode.shapeFlag |= type
}
const handlersRE = /^on|^vnode/
export function mergeProps(...args: (Data & VNodeProps)[]) {
const ret = extend({}, args[0])
for (let i = 1; i < args.length; i++) {
@ -596,8 +595,7 @@ export function mergeProps(...args: (Data & VNodeProps)[]) {
}
} else if (key === 'style') {
ret.style = normalizeStyle([ret.style, toMerge.style])
} else if (handlersRE.test(key)) {
// on*, vnode*
} else if (isOn(key)) {
const existing = ret[key]
const incoming = toMerge[key]
if (existing !== incoming) {

View File

@ -3,7 +3,7 @@ import { patchStyle } from './modules/style'
import { patchAttr } from './modules/attrs'
import { patchDOMProp } from './modules/props'
import { patchEvent } from './modules/events'
import { isOn, isString, isFunction } from '@vue/shared'
import { isOn, isString, isFunction, isModelListener } from '@vue/shared'
import { RendererOptions } from '@vue/runtime-core'
const nativeOnRE = /^on[a-z]/
@ -35,7 +35,7 @@ export const patchProp: DOMRendererOptions['patchProp'] = (
default:
if (isOn(key)) {
// ignore v-model listeners
if (!key.startsWith('onUpdate:')) {
if (!isModelListener(key)) {
patchEvent(el, key, prevValue, nextValue, parentComponent)
}
} else if (shouldSetAsProp(el, key, nextValue, isSVG)) {

View File

@ -41,6 +41,8 @@ export const NO = () => false
const onRE = /^on[^a-z]/
export const isOn = (key: string) => onRE.test(key)
export const isModelListener = (key: string) => key.startsWith('onUpdate:')
export const extend = Object.assign
export const remove = <T>(arr: T[], el: T) => {