diff --git a/packages/core/src/componentProps.ts b/packages/core/src/componentProps.ts index 069f5cdc..dd2de0ed 100644 --- a/packages/core/src/componentProps.ts +++ b/packages/core/src/componentProps.ts @@ -50,21 +50,27 @@ export function normalizeComponentProps( const res: Data = {} if (raw) { for (const key in raw) { - if (key === 'key' || key === 'ref' || key === 'slot') { + // key, ref, slots are reserved + if (key === 'key' || key === 'ref' || key === 'slots') { continue } - if (hasDeclaredProps) { - if (options.hasOwnProperty(key)) { - if (__DEV__) { - validateProp(key, raw[key], options[key], Component) - } - res[key] = raw[key] - } else { - // when props are explicitly declared, any non-matching prop is - // extracted into attrs instead. - ;(res.attrs || (res.attrs = {}))[key] = raw[key] - } + // class, style & nativeOn are always extracted into a separate `attrs` + // object, which can then be merged onto child component root. + // in addition, if the component has explicitly declared props, then + // any non-matching props are extracted into `attrs` as well. + let isNativeOn + if ( + key === 'class' || + key === 'style' || + (isNativeOn = key.startsWith('nativeOn')) || + (hasDeclaredProps && !options.hasOwnProperty(key)) + ) { + const newKey = isNativeOn ? 'on' + key.slice(8) : key + ;(res.attrs || (res.attrs = {}))[newKey] = raw[key] } else { + if (__DEV__ && hasDeclaredProps && options.hasOwnProperty(key)) { + validateProp(key, raw[key], options[key], Component) + } res[key] = raw[key] } } diff --git a/packages/core/src/componentUtils.ts b/packages/core/src/componentUtils.ts index d4c4dc7e..4b32e903 100644 --- a/packages/core/src/componentUtils.ts +++ b/packages/core/src/componentUtils.ts @@ -109,30 +109,8 @@ export function normalizeComponentRoot( (flags & VNodeFlags.COMPONENT || flags & VNodeFlags.ELEMENT) ) { const parentData = componentVNode.data - if (parentData != null && inheritAttrs !== false) { - let extraData: any = null - for (const key in parentData) { - // attrs/class/style bindings on parentVNode are merged down to child - // component root, - // nativeOn* handlers are merged to child root as normal on* handlers. - // cloneVNode contains special logic for merging these props with - // existing values. - if (key === 'attrs') { - extraData = extraData || {} - const { attrs } = parentData - for (const attr in attrs) { - extraData[attr] = attrs[attr] - } - } else if (key === 'class' || key === 'style') { - ;(extraData || (extraData = {}))[key] = parentData[key] - } else if (key.startsWith('nativeOn')) { - ;(extraData || (extraData = {}))['on' + key.slice(8)] = - parentData[key] - } - } - if (extraData) { - vnode = cloneVNode(vnode, extraData) - } + if (inheritAttrs !== false && parentData && parentData.attrs) { + vnode = cloneVNode(vnode, parentData.attrs) } if (vnode.el) { vnode = cloneVNode(vnode)