$vnode: VNode
$data: D
$props: P
+ $attrs: Data
$computed: Data
$slots: Slots
$root: MountedComponent
public $parentVNode: VNode | null = null
public $data: Data | null = null
public $props: Data | null = null
+ public $attrs: Data | null = null
public $computed: Data | null = null
public $slots: Slots | null = null
public $root: MountedComponent | null = null
export type Data = Record<string, any>
export interface RenderFunction<P = Data> {
- (props: P, slots: Slots): any
+ (props: P, slots: Slots, attrs: Data): any
}
export interface ComponentOptions<D = Data, P = Data> {
import { EMPTY_OBJ } from './utils'
-import { Component, ComponentClass, MountedComponent } from './component'
+import {
+ Component,
+ ComponentClass,
+ MountedComponent,
+ FunctionalComponent
+} from './component'
import { immutable, unwrap, lock, unlock } from '@vue/observer'
import {
Data,
PropOptions
} from './componentOptions'
-export function initializeProps(instance: Component, props: Data | null) {
+export function initializeProps(instance: Component, data: Data | null) {
+ const { props, attrs } = resolveProps(
+ data,
+ instance.$options.props,
+ instance.constructor as ComponentClass
+ )
instance.$props = immutable(props || {})
+ instance.$attrs = immutable(attrs || {})
}
-export function updateProps(instance: MountedComponent, nextProps: Data) {
- // instance.$props is an observable that should not be replaced.
- // instead, we mutate it to match latest props, which will trigger updates
- // if any value has changed.
- if (nextProps != null) {
- const props = instance.$props
- const rawProps = unwrap(props)
+export function updateProps(instance: MountedComponent, nextData: Data) {
+ // instance.$props and instance.$attrs are observables that should not be
+ // replaced. Instead, we mutate them to match latest props, which will trigger
+ // updates if any value that's been used in child component has changed.
+ if (nextData != null) {
+ const { props: nextProps, attrs: nextAttrs } = resolveProps(
+ nextData,
+ instance.$options.props,
+ instance.constructor as ComponentClass
+ )
// unlock to temporarily allow mutatiing props
unlock()
+ const props = instance.$props
+ const rawProps = unwrap(props)
for (const key in rawProps) {
if (!nextProps.hasOwnProperty(key)) {
delete props[key]
for (const key in nextProps) {
props[key] = nextProps[key]
}
+ if (nextAttrs) {
+ const attrs = instance.$attrs
+ const rawAttrs = unwrap(attrs)
+ for (const key in rawAttrs) {
+ if (!nextAttrs.hasOwnProperty(key)) {
+ delete attrs[key]
+ }
+ }
+ for (const key in nextAttrs) {
+ attrs[key] = nextAttrs[key]
+ }
+ }
lock()
}
}
-// This is called for every component vnode created. This also means the data
-// on every component vnode is guarunteed to be a fresh object.
-export function normalizeComponentProps(
+const EMPTY_PROPS = { props: EMPTY_OBJ }
+
+// resolve raw VNode data.
+// - filter out reserved keys (key, ref, slots)
+// - extract class, style and nativeOn* into $attrs (to be merged onto child
+// component root)
+// - for the rest:
+// - if has declared props: put declared ones in `props`, the rest in `attrs`
+// - else: everything goes in `props`.
+export function resolveProps(
raw: any,
- rawOptions: ComponentPropsOptions,
- Component: ComponentClass
-): Data {
+ rawOptions: ComponentPropsOptions | void,
+ Component: ComponentClass | FunctionalComponent
+): { props: Data; attrs?: Data } {
const hasDeclaredProps = rawOptions !== void 0
const options = (hasDeclaredProps &&
- normalizePropsOptions(rawOptions)) as NormalizedPropsOptions
+ normalizePropsOptions(
+ rawOptions as ComponentPropsOptions
+ )) as NormalizedPropsOptions
if (!raw && !hasDeclaredProps) {
- return EMPTY_OBJ
+ return EMPTY_PROPS
}
- const res: Data = {}
+ const props: any = {}
+ let attrs: any = void 0
if (raw) {
for (const key in raw) {
// key, ref, slots are reserved
(hasDeclaredProps && !options.hasOwnProperty(key))
) {
const newKey = isNativeOn ? 'on' + key.slice(8) : key
- ;(res.attrs || (res.attrs = {}))[newKey] = raw[key]
+ ;(attrs || (attrs = {}))[newKey] = raw[key]
} else {
if (__DEV__ && hasDeclaredProps && options.hasOwnProperty(key)) {
validateProp(key, raw[key], options[key], Component)
}
- res[key] = raw[key]
+ props[key] = raw[key]
}
}
}
// set default values
if (hasDeclaredProps) {
for (const key in options) {
- if (res[key] === void 0) {
+ if (props[key] === void 0) {
const opt = options[key]
if (opt != null && opt.hasOwnProperty('default')) {
const defaultValue = opt.default
- res[key] =
+ props[key] =
typeof defaultValue === 'function' ? defaultValue() : defaultValue
}
}
}
}
- return res
+ return { props, attrs }
}
-const normalizeCache: WeakMap<
+const normalizeCache = new WeakMap<
ComponentPropsOptions,
NormalizedPropsOptions
-> = new WeakMap()
+>()
+
function normalizePropsOptions(
raw: ComponentPropsOptions
): NormalizedPropsOptions {
key: string,
value: any,
validator: PropValidator<any>,
- Component: ComponentClass
+ Component: ComponentClass | FunctionalComponent
) {
// TODO
}
return normalizeComponentRoot(
vnode,
instance.$parentVNode,
+ instance.$attrs,
instance.$options.inheritAttrs
)
}
export function normalizeComponentRoot(
vnode: any,
componentVNode: VNode | null,
+ attrs: Data | void,
inheritAttrs: boolean | void
): VNode {
if (vnode == null) {
componentVNode &&
(flags & VNodeFlags.COMPONENT || flags & VNodeFlags.ELEMENT)
) {
- const parentData = componentVNode.data
- if (inheritAttrs !== false && parentData && parentData.attrs) {
- vnode = cloneVNode(vnode, parentData.attrs)
+ if (inheritAttrs !== false && attrs !== void 0) {
+ // TODO should merge
+ console.log(attrs)
+ vnode = cloneVNode(vnode, attrs)
}
if (vnode.el) {
vnode = cloneVNode(vnode)
FunctionalComponent,
ComponentClass
} from './component'
-import { updateProps } from './componentProps'
+import { updateProps, resolveProps } from './componentProps'
import {
renderInstanceRoot,
createComponentInstance,
)
} else {
// functional component
+ const render = tag as FunctionalComponent
+ const { props, attrs } = resolveProps(data, render.props, render)
const subTree = (vnode.children = normalizeComponentRoot(
- (tag as FunctionalComponent)(data || EMPTY_OBJ, slots || EMPTY_OBJ),
+ render(props, slots || EMPTY_OBJ, attrs || EMPTY_OBJ),
vnode,
- (tag as FunctionalComponent).inheritAttrs
+ attrs,
+ render.inheritAttrs
))
- el = vnode.el = mount(subTree, null, parentComponent, isSVG, null)
+ el = vnode.el = mount(subTree, null, parentComponent, isSVG, endNode)
}
if (container != null) {
insertOrAppend(container, el, endNode)
function patchStatefulComponent(prevVNode: VNode, nextVNode: VNode) {
const { childFlags: prevChildFlags } = prevVNode
const {
- data: nextProps,
+ data: nextData,
slots: nextSlots,
childFlags: nextChildFlags
} = nextVNode
instance.$parentVNode = nextVNode
// Update props. This will trigger child update if necessary.
- if (nextProps !== null) {
- updateProps(instance, nextProps)
+ if (nextData !== null) {
+ updateProps(instance, nextData)
}
// If has different slots content, or has non-compiled slots,
isSVG: boolean
) {
// functional component tree is stored on the vnode as `children`
- const { data: prevProps, slots: prevSlots } = prevVNode
- const { data: nextProps, slots: nextSlots } = nextVNode
+ const { data: prevData, slots: prevSlots } = prevVNode
+ const { data: nextData, slots: nextSlots } = nextVNode
const render = nextVNode.tag as FunctionalComponent
const prevTree = prevVNode.children as VNode
let shouldUpdate = true
if (render.pure && prevSlots == null && nextSlots == null) {
- shouldUpdate = shouldUpdateFunctionalComponent(prevProps, nextProps)
+ shouldUpdate = shouldUpdateFunctionalComponent(prevData, nextData)
}
if (shouldUpdate) {
+ const { props, attrs } = resolveProps(nextData, render.props, render)
const nextTree = (nextVNode.children = normalizeComponentRoot(
- render(nextProps || EMPTY_OBJ, nextSlots || EMPTY_OBJ),
+ render(props, nextSlots || EMPTY_OBJ, attrs || EMPTY_OBJ),
nextVNode,
+ attrs,
render.inheritAttrs
))
patch(prevTree, nextTree, container, parentComponent, isSVG)
FunctionalComponent
} from './component'
import { VNodeFlags, ChildrenFlags } from './flags'
-import { normalizeComponentProps } from './componentProps'
import { createComponentClassFromOptions } from './componentUtils'
-import { ComponentPropsOptions } from './componentOptions'
// Vue core is platform agnostic, so we are not using Element for "DOM" nodes.
export interface RenderNode {
) {
// resolve type
let flags: VNodeFlags
- let propsOptions: ComponentPropsOptions
// flags
const compType = typeof comp
comp._normalized = true
}
comp = render
- propsOptions = comp.props
} else {
// object literal stateful
flags = VNodeFlags.COMPONENT_STATEFUL
comp =
comp._normalized ||
(comp._normalized = createComponentClassFromOptions(comp))
- propsOptions = comp.options && comp.options.props
}
} else {
// assumes comp is function here now
}
if (comp.prototype && comp.prototype.render) {
flags = VNodeFlags.COMPONENT_STATEFUL
- propsOptions = comp.options && comp.options.props
} else {
flags = VNodeFlags.COMPONENT_FUNCTIONAL
- propsOptions = comp.props
}
}
// TODO warn functional component cannot have ref
}
- // props
- const props = normalizeComponentProps(data, propsOptions, comp)
-
// slots
let slots: any
if (childFlags == null) {
return createVNode(
flags,
comp,
- props,
+ data,
null, // to be set during mount
childFlags,
key,
}
}
for (const key in extraData) {
- 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
- }
+ clonedData[key] = extraData[key]
}
}
return createVNode(
if (typeof value === 'number' && !nonNumericRE.test(key)) {
value = value + 'px'
}
- style.setProperty(key, value)
+ style[key] = value
}
if (prev && typeof prev !== 'string') {
prev = normalizeStyle(prev)
for (const key in prev) {
if (!normalizedNext[key]) {
- style.setProperty(key, '')
+ style[key] = ''
}
}
}