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,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 || | ||||
|  | ||||
| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user