fix(v-model): v-model listeners should not fallthrough to plain element root
fix #1643
This commit is contained in:
parent
304830a764
commit
c852bf18d7
@ -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,50 +118,56 @@ export function renderComponentRoot(
|
||||
;[root, setRoot] = getChildRoot(result)
|
||||
}
|
||||
|
||||
if (
|
||||
Component.inheritAttrs !== false &&
|
||||
fallthroughAttrs &&
|
||||
Object.keys(fallthroughAttrs).length
|
||||
) {
|
||||
if (
|
||||
root.shapeFlag & ShapeFlags.ELEMENT ||
|
||||
root.shapeFlag & ShapeFlags.COMPONENT
|
||||
) {
|
||||
root = cloneVNode(root, fallthroughAttrs)
|
||||
} else if (__DEV__ && !accessedAttrs && root.type !== Comment) {
|
||||
const allAttrs = Object.keys(attrs)
|
||||
const eventAttrs: string[] = []
|
||||
const extraAttrs: string[] = []
|
||||
for (let i = 0, l = allAttrs.length; i < l; i++) {
|
||||
const key = allAttrs[i]
|
||||
if (isOn(key)) {
|
||||
// ignore v-model handlers when they fail to fallthrough
|
||||
if (!key.startsWith('onUpdate:')) {
|
||||
// remove `on`, lowercase first letter to reflect event casing
|
||||
// accurately
|
||||
eventAttrs.push(key[2].toLowerCase() + key.slice(3))
|
||||
}
|
||||
} else {
|
||||
extraAttrs.push(key)
|
||||
if (Component.inheritAttrs !== false && fallthroughAttrs) {
|
||||
const keys = Object.keys(fallthroughAttrs)
|
||||
const { shapeFlag } = root
|
||||
if (keys.length) {
|
||||
if (
|
||||
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)
|
||||
const eventAttrs: string[] = []
|
||||
const extraAttrs: string[] = []
|
||||
for (let i = 0, l = allAttrs.length; i < l; i++) {
|
||||
const key = allAttrs[i]
|
||||
if (isOn(key)) {
|
||||
// ignore v-model handlers when they fail to fallthrough
|
||||
if (!isModelListener(key)) {
|
||||
// remove `on`, lowercase first letter to reflect event casing
|
||||
// accurately
|
||||
eventAttrs.push(key[2].toLowerCase() + key.slice(3))
|
||||
}
|
||||
} else {
|
||||
extraAttrs.push(key)
|
||||
}
|
||||
}
|
||||
if (extraAttrs.length) {
|
||||
warn(
|
||||
`Extraneous non-props attributes (` +
|
||||
`${extraAttrs.join(', ')}) ` +
|
||||
`were passed to component but could not be automatically inherited ` +
|
||||
`because component renders fragment or text root nodes.`
|
||||
)
|
||||
}
|
||||
if (eventAttrs.length) {
|
||||
warn(
|
||||
`Extraneous non-emits event listeners (` +
|
||||
`${eventAttrs.join(', ')}) ` +
|
||||
`were passed to component but could not be automatically inherited ` +
|
||||
`because component renders fragment or text root nodes. ` +
|
||||
`If the listener is intended to be a component custom event listener only, ` +
|
||||
`declare it using the "emits" option.`
|
||||
)
|
||||
}
|
||||
}
|
||||
if (extraAttrs.length) {
|
||||
warn(
|
||||
`Extraneous non-props attributes (` +
|
||||
`${extraAttrs.join(', ')}) ` +
|
||||
`were passed to component but could not be automatically inherited ` +
|
||||
`because component renders fragment or text root nodes.`
|
||||
)
|
||||
}
|
||||
if (eventAttrs.length) {
|
||||
warn(
|
||||
`Extraneous non-emits event listeners (` +
|
||||
`${eventAttrs.join(', ')}) ` +
|
||||
`were passed to component but could not be automatically inherited ` +
|
||||
`because component renders fragment or text root nodes. ` +
|
||||
`If the listener is intended to be a component custom event listener only, ` +
|
||||
`declare it using the "emits" option.`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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 ||
|
||||
|
@ -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) {
|
||||
|
@ -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)) {
|
||||
|
@ -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) => {
|
||||
|
Loading…
Reference in New Issue
Block a user