refactor: adjust props initialization/updating

This commit is contained in:
Evan You 2018-10-26 17:49:40 -04:00
parent e05673f4d3
commit b08b15dba0
3 changed files with 39 additions and 34 deletions

View File

@ -19,8 +19,6 @@ import {
} from '@vue/shared' } from '@vue/shared'
import { warn } from './warning' import { warn } from './warning'
const EMPTY_PROPS = { props: EMPTY_OBJ }
const enum BooleanFlags { const enum BooleanFlags {
shouldCast = '1', shouldCast = '1',
shouldCastTrue = '2' shouldCastTrue = '2'
@ -38,9 +36,11 @@ export function initializeProps(
options: NormalizedPropsOptions | undefined, options: NormalizedPropsOptions | undefined,
data: Data | null data: Data | null
) { ) {
const { props, attrs } = resolveProps(data, options) const [props, attrs] = resolveProps(data, options)
instance.$props = immutable(props || {}) instance.$props = immutable(props === EMPTY_OBJ ? {} : props)
instance.$attrs = immutable(attrs || {}) instance.$attrs = options
? immutable(attrs === EMPTY_OBJ ? {} : attrs)
: instance.$props
} }
// resolve raw VNode data. // resolve raw VNode data.
@ -50,10 +50,13 @@ export function initializeProps(
// - for the rest: // - for the rest:
// - if has declared props: put declared ones in `props`, the rest in `attrs` // - if has declared props: put declared ones in `props`, the rest in `attrs`
// - else: everything goes in `props`. // - else: everything goes in `props`.
const EMPTY_PROPS = [EMPTY_OBJ, EMPTY_OBJ] as [Data, Data]
export function resolveProps( export function resolveProps(
rawData: any, rawData: any,
_options: NormalizedPropsOptions | void _options: NormalizedPropsOptions | void
): { props: Data; attrs?: Data } { ): [Data, Data] {
const hasDeclaredProps = _options !== void 0 const hasDeclaredProps = _options !== void 0
const options = _options as NormalizedPropsOptions const options = _options as NormalizedPropsOptions
if (!rawData && !hasDeclaredProps) { if (!rawData && !hasDeclaredProps) {
@ -109,44 +112,48 @@ export function resolveProps(
// if component has no declared props, $attrs === $props // if component has no declared props, $attrs === $props
attrs = props attrs = props
} }
return { props, attrs } return [props, attrs]
} }
export function updateProps(instance: ComponentInstance, nextData: Data) { export function updateProps(
instance: ComponentInstance,
nextData: Data | null
) {
// instance.$props and instance.$attrs are observables that should not be // instance.$props and instance.$attrs are observables that should not be
// replaced. Instead, we mutate them to match latest props, which will trigger // 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. // updates if any value that's been used in child component has changed.
if (nextData != null) { const [nextProps, nextAttrs] = resolveProps(nextData, instance.$options.props)
const { props: nextProps, attrs: nextAttrs } = resolveProps( // unlock to temporarily allow mutatiing props
nextData, unlock()
instance.$options.props const props = instance.$props
) const rawProps = unwrap(props)
// unlock to temporarily allow mutatiing props const hasEmptyProps = nextProps === EMPTY_OBJ
unlock() for (const key in rawProps) {
const props = instance.$props if (hasEmptyProps || !nextProps.hasOwnProperty(key)) {
const rawProps = unwrap(props) delete (props as any)[key]
for (const key in rawProps) {
if (!nextProps.hasOwnProperty(key)) {
delete (props as any)[key]
}
} }
}
if (!hasEmptyProps) {
for (const key in nextProps) { for (const key in nextProps) {
;(props as any)[key] = nextProps[key] ;(props as any)[key] = nextProps[key]
} }
if (nextAttrs) { }
const attrs = instance.$attrs const attrs = instance.$attrs
const rawAttrs = unwrap(attrs) if (attrs !== props) {
for (const key in rawAttrs) { const rawAttrs = unwrap(attrs)
if (!nextAttrs.hasOwnProperty(key)) { const hasEmptyAttrs = nextAttrs === EMPTY_OBJ
delete attrs[key] for (const key in rawAttrs) {
} if (hasEmptyAttrs || !nextAttrs.hasOwnProperty(key)) {
delete attrs[key]
} }
}
if (!hasEmptyAttrs) {
for (const key in nextAttrs) { for (const key in nextAttrs) {
attrs[key] = nextAttrs[key] attrs[key] = nextAttrs[key]
} }
} }
lock()
} }
lock()
} }
export function normalizePropsOptions( export function normalizePropsOptions(

View File

@ -119,10 +119,10 @@ export function renderInstanceRoot(instance: ComponentInstance): VNode {
export function renderFunctionalRoot(vnode: VNode): VNode { export function renderFunctionalRoot(vnode: VNode): VNode {
const render = vnode.tag as FunctionalComponent const render = vnode.tag as FunctionalComponent
const { props, attrs } = resolveProps(vnode.data, render.props) const [props, attrs] = resolveProps(vnode.data, render.props)
let subTree let subTree
try { try {
subTree = render(props, vnode.slots || EMPTY_OBJ, attrs || EMPTY_OBJ, vnode) subTree = render(props, vnode.slots || EMPTY_OBJ, attrs, vnode)
} catch (err) { } catch (err) {
handleError(err, vnode, ErrorTypes.RENDER) handleError(err, vnode, ErrorTypes.RENDER)
} }

View File

@ -480,9 +480,7 @@ export function createRenderer(options: RendererOptions) {
instance.$parentVNode = nextVNode as MountedVNode instance.$parentVNode = nextVNode as MountedVNode
// Update props. This will trigger child update if necessary. // Update props. This will trigger child update if necessary.
if (nextData !== null) { updateProps(instance, nextData)
updateProps(instance, nextData)
}
// If has different slots content, or has non-compiled slots, // If has different slots content, or has non-compiled slots,
// the child needs to be force updated. It's ok to call $forceUpdate // the child needs to be force updated. It's ok to call $forceUpdate