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 { Component, ComponentClass, MountedComponent } from './component'
import { immutable, unwrap, lock, unlock } from '@vue/observer' import { immutable, unwrap, lock, unlock } from '@vue/observer'
import { import {
@ -38,29 +38,42 @@ export function updateProps(instance: MountedComponent, nextProps: Data) {
// on every component vnode is guarunteed to be a fresh object. // on every component vnode is guarunteed to be a fresh object.
export function normalizeComponentProps( export function normalizeComponentProps(
raw: any, raw: any,
options: ComponentPropsOptions, rawOptions: ComponentPropsOptions,
Component: ComponentClass Component: ComponentClass
): Data { ): Data {
if (!raw) { const hasDeclaredProps = rawOptions !== void 0
const options = (hasDeclaredProps &&
normalizePropsOptions(rawOptions)) as NormalizedPropsOptions
if (!raw && !hasDeclaredProps) {
return EMPTY_OBJ return EMPTY_OBJ
} }
const res: Data = {} const res: Data = {}
const normalizedOptions = options && normalizePropsOptions(options) if (raw) {
for (const key in raw) { for (const key in raw) {
if (isReservedProp(key)) { if (key === 'key' || key === 'ref' || key === 'slot') {
continue continue
} }
if (__DEV__ && normalizedOptions != null) { if (hasDeclaredProps) {
validateProp(key, raw[key], normalizedOptions[key], Component) if (options.hasOwnProperty(key)) {
} else { if (__DEV__) {
res[key] = raw[key] 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 // set default values
if (normalizedOptions != null) { if (hasDeclaredProps) {
for (const key in normalizedOptions) { for (const key in options) {
if (res[key] === void 0) { if (res[key] === void 0) {
const opt = normalizedOptions[key] const opt = options[key]
if (opt != null && opt.hasOwnProperty('default')) { if (opt != null && opt.hasOwnProperty('default')) {
const defaultValue = opt.default const defaultValue = opt.default
res[key] = res[key] =

View File

@ -103,30 +103,31 @@ export function normalizeComponentRoot(
componentVNode && componentVNode &&
(flags & VNodeFlags.COMPONENT || flags & VNodeFlags.ELEMENT) (flags & VNodeFlags.COMPONENT || flags & VNodeFlags.ELEMENT)
) { ) {
const parentData = componentVNode.data || EMPTY_OBJ const parentData = componentVNode.data
const childData = vnode.data || EMPTY_OBJ if (parentData != null) {
let extraData: any = null let extraData: any = null
for (const key in parentData) { for (const key in parentData) {
// class/style bindings on parentVNode are merged down to child // attrs/class/style bindings on parentVNode are merged down to child
// component root. // component root,
if (key === 'class') { // nativeOn* handlers are merged to child root as normal on* handlers.
;(extraData || (extraData = {})).class = childData.class // cloneVNode contains special logic for merging these props with
? [].concat(childData.class, parentData.class) // existing values.
: parentData.class if (key === 'attrs') {
} else if (key === 'style') { extraData = extraData || {}
;(extraData || (extraData = {})).style = childData.style const { attrs } = parentData
? [].concat(childData.style, parentData.style) for (const attr in attrs) {
: parentData.style extraData[attr] = attrs[attr]
} else if (key.startsWith('nativeOn')) { }
// nativeOn* handlers are merged down to child root as native listeners } else if (key === 'class' || key === 'style') {
const event = 'on' + key.slice(8) ;(extraData || (extraData = {}))[key] = parentData[key]
;(extraData || (extraData = {}))[event] = childData.event } else if (key.startsWith('nativeOn')) {
? [].concat(childData.event, parentData[key]) ;(extraData || (extraData = {}))['on' + key.slice(8)] =
: parentData[key] parentData[key]
}
}
if (extraData) {
vnode = cloneVNode(vnode, extraData)
} }
}
if (extraData) {
vnode = cloneVNode(vnode, extraData)
} }
if (vnode.el) { if (vnode.el) {
vnode = cloneVNode(vnode) vnode = cloneVNode(vnode)

View File

@ -258,7 +258,28 @@ export function cloneVNode(vnode: VNode, extraData?: VNodeData): VNode {
} }
} }
for (const key in extraData) { 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( return createVNode(