wip: refactor attrs inheritance
This commit is contained in:
parent
ef1d621162
commit
85cd69a988
@ -30,6 +30,7 @@ export interface MountedComponent<D = Data, P = Data> extends Component {
|
||||
$vnode: VNode
|
||||
$data: D
|
||||
$props: P
|
||||
$attrs: Data
|
||||
$computed: Data
|
||||
$slots: Slots
|
||||
$root: MountedComponent
|
||||
@ -70,6 +71,7 @@ export class Component {
|
||||
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
|
||||
|
@ -4,7 +4,7 @@ import { MountedComponent } from './component'
|
||||
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> {
|
||||
|
@ -1,5 +1,10 @@
|
||||
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,
|
||||
@ -9,19 +14,30 @@ import {
|
||||
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]
|
||||
@ -30,24 +46,46 @@ export function updateProps(instance: MountedComponent, nextProps: Data) {
|
||||
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
|
||||
@ -66,35 +104,36 @@ export function normalizeComponentProps(
|
||||
(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 {
|
||||
@ -116,7 +155,7 @@ function validateProp(
|
||||
key: string,
|
||||
value: any,
|
||||
validator: PropValidator<any>,
|
||||
Component: ComponentClass
|
||||
Component: ComponentClass | FunctionalComponent
|
||||
) {
|
||||
// TODO
|
||||
}
|
||||
|
@ -74,6 +74,7 @@ export function renderInstanceRoot(instance: MountedComponent) {
|
||||
return normalizeComponentRoot(
|
||||
vnode,
|
||||
instance.$parentVNode,
|
||||
instance.$attrs,
|
||||
instance.$options.inheritAttrs
|
||||
)
|
||||
}
|
||||
@ -93,6 +94,7 @@ export function teardownComponentInstance(instance: MountedComponent) {
|
||||
export function normalizeComponentRoot(
|
||||
vnode: any,
|
||||
componentVNode: VNode | null,
|
||||
attrs: Data | void,
|
||||
inheritAttrs: boolean | void
|
||||
): VNode {
|
||||
if (vnode == null) {
|
||||
@ -108,9 +110,10 @@ export function normalizeComponentRoot(
|
||||
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)
|
||||
|
@ -18,7 +18,7 @@ import {
|
||||
FunctionalComponent,
|
||||
ComponentClass
|
||||
} from './component'
|
||||
import { updateProps } from './componentProps'
|
||||
import { updateProps, resolveProps } from './componentProps'
|
||||
import {
|
||||
renderInstanceRoot,
|
||||
createComponentInstance,
|
||||
@ -272,12 +272,15 @@ export function createRenderer(options: RendererOptions) {
|
||||
)
|
||||
} 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)
|
||||
@ -508,7 +511,7 @@ export function createRenderer(options: RendererOptions) {
|
||||
function patchStatefulComponent(prevVNode: VNode, nextVNode: VNode) {
|
||||
const { childFlags: prevChildFlags } = prevVNode
|
||||
const {
|
||||
data: nextProps,
|
||||
data: nextData,
|
||||
slots: nextSlots,
|
||||
childFlags: nextChildFlags
|
||||
} = nextVNode
|
||||
@ -519,8 +522,8 @@ export function createRenderer(options: RendererOptions) {
|
||||
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,
|
||||
@ -546,20 +549,22 @@ export function createRenderer(options: RendererOptions) {
|
||||
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)
|
||||
|
@ -4,9 +4,7 @@ import {
|
||||
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 {
|
||||
@ -119,7 +117,6 @@ export function createComponentVNode(
|
||||
) {
|
||||
// resolve type
|
||||
let flags: VNodeFlags
|
||||
let propsOptions: ComponentPropsOptions
|
||||
|
||||
// flags
|
||||
const compType = typeof comp
|
||||
@ -134,14 +131,12 @@ export function createComponentVNode(
|
||||
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
|
||||
@ -150,10 +145,8 @@ export function createComponentVNode(
|
||||
}
|
||||
if (comp.prototype && comp.prototype.render) {
|
||||
flags = VNodeFlags.COMPONENT_STATEFUL
|
||||
propsOptions = comp.options && comp.options.props
|
||||
} else {
|
||||
flags = VNodeFlags.COMPONENT_FUNCTIONAL
|
||||
propsOptions = comp.props
|
||||
}
|
||||
}
|
||||
|
||||
@ -161,9 +154,6 @@ export function createComponentVNode(
|
||||
// TODO warn functional component cannot have ref
|
||||
}
|
||||
|
||||
// props
|
||||
const props = normalizeComponentProps(data, propsOptions, comp)
|
||||
|
||||
// slots
|
||||
let slots: any
|
||||
if (childFlags == null) {
|
||||
@ -188,7 +178,7 @@ export function createComponentVNode(
|
||||
return createVNode(
|
||||
flags,
|
||||
comp,
|
||||
props,
|
||||
data,
|
||||
null, // to be set during mount
|
||||
childFlags,
|
||||
key,
|
||||
@ -258,28 +248,7 @@ export function cloneVNode(vnode: VNode, extraData?: VNodeData): VNode {
|
||||
}
|
||||
}
|
||||
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(
|
||||
|
@ -22,13 +22,13 @@ export function patchStyle(el: any, prev: any, next: any, data: any) {
|
||||
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] = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user