feat: attribute fallthrough

This commit is contained in:
Evan You 2018-09-23 21:54:48 -04:00
parent 6ce39b4d20
commit cb01733842
3 changed files with 74 additions and 39 deletions

View File

@ -1,4 +1,4 @@
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 {
@ -38,29 +38,42 @@ export function updateProps(instance: MountedComponent, nextProps: Data) {
// 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] =

View File

@ -103,30 +103,31 @@ export function normalizeComponentRoot(
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)

View File

@ -258,7 +258,28 @@ export function cloneVNode(vnode: VNode, extraData?: VNodeData): 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(