-import { EMPTY_OBJ, isReservedProp } from './utils'
+import { EMPTY_OBJ } from './utils'
import { Component, ComponentClass, MountedComponent } from './component'
import { immutable, unwrap, lock, unlock } from '@vue/observer'
import {
// on every component vnode is guarunteed to be a fresh object.
export function normalizeComponentProps(
raw: any,
- options: ComponentPropsOptions,
+ rawOptions: ComponentPropsOptions,
Component: ComponentClass
): Data {
- if (!raw) {
+ const hasDeclaredProps = rawOptions !== void 0
+ const options = (hasDeclaredProps &&
+ normalizePropsOptions(rawOptions)) as NormalizedPropsOptions
+ if (!raw && !hasDeclaredProps) {
return EMPTY_OBJ
}
const res: Data = {}
- const normalizedOptions = options && normalizePropsOptions(options)
- for (const key in raw) {
- if (isReservedProp(key)) {
- continue
- }
- if (__DEV__ && normalizedOptions != null) {
- validateProp(key, raw[key], normalizedOptions[key], Component)
- } else {
- res[key] = raw[key]
+ if (raw) {
+ for (const key in raw) {
+ if (key === 'key' || key === 'ref' || key === 'slot') {
+ 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]
+ }
+ } else {
+ res[key] = raw[key]
+ }
}
}
// set default values
- if (normalizedOptions != null) {
- for (const key in normalizedOptions) {
+ if (hasDeclaredProps) {
+ for (const key in options) {
if (res[key] === void 0) {
- const opt = normalizedOptions[key]
+ const opt = options[key]
if (opt != null && opt.hasOwnProperty('default')) {
const defaultValue = opt.default
res[key] =
componentVNode &&
(flags & VNodeFlags.COMPONENT || flags & VNodeFlags.ELEMENT)
) {
- const parentData = componentVNode.data || EMPTY_OBJ
- const childData = vnode.data || EMPTY_OBJ
- let extraData: any = null
- for (const key in parentData) {
- // class/style bindings on parentVNode are merged down to child
- // component root.
- if (key === 'class') {
- ;(extraData || (extraData = {})).class = childData.class
- ? [].concat(childData.class, parentData.class)
- : parentData.class
- } else if (key === 'style') {
- ;(extraData || (extraData = {})).style = childData.style
- ? [].concat(childData.style, parentData.style)
- : parentData.style
- } else if (key.startsWith('nativeOn')) {
- // nativeOn* handlers are merged down to child root as native listeners
- const event = 'on' + key.slice(8)
- ;(extraData || (extraData = {}))[event] = childData.event
- ? [].concat(childData.event, parentData[key])
- : parentData[key]
+ const parentData = componentVNode.data
+ if (parentData != null) {
+ 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 (extraData) {
- vnode = cloneVNode(vnode, extraData)
}
if (vnode.el) {
vnode = cloneVNode(vnode)
}
}
for (const key in extraData) {
- clonedData[key] = extraData[key]
+ const existing = clonedData[key]
+ const extra = extraData[key]
+ if (extra === void 0) {
+ continue
+ }
+ // special merge behavior for attrs / class / style / on.
+ let isOn
+ if (key === 'attrs') {
+ clonedData.attrs = existing
+ ? Object.assign({}, existing, extra)
+ : extra
+ } else if (
+ key === 'class' ||
+ key === 'style' ||
+ (isOn = key.startsWith('on'))
+ ) {
+ // all three props can handle array format, so we simply merge them
+ // by concating.
+ clonedData[key] = existing ? [].concat(existing, extra) : extra
+ } else {
+ clonedData[key] = extra
+ }
}
}
return createVNode(