From d6d4ba86799d8691fc7d0b8316821f40f7813f39 Mon Sep 17 00:00:00 2001 From: Evan You Date: Sat, 1 Jun 2019 17:43:41 +0800 Subject: [PATCH] wip: style/class normalization --- packages/runtime-core/src/componentProps.ts | 16 ++--- packages/runtime-core/src/createRenderer.ts | 7 ++- packages/runtime-core/src/vnode.ts | 65 ++++++++++++++++++--- 3 files changed, 68 insertions(+), 20 deletions(-) diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index 7e4fac10..77c07c65 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -36,12 +36,10 @@ type RequiredKeys = { type OptionalKeys = Exclude> type InferPropType = T extends null - ? any - : // null & true would fail to infer - T extends { type: null | true } - ? any - : // somehow `ObjectContructor` when inferred from { (): T } becomes `any` - T extends ObjectConstructor | { type: ObjectConstructor } + ? any // null & true would fail to infer + : T extends { type: null | true } + ? any // somehow `ObjectContructor` when inferred from { (): T } becomes `any` + : T extends ObjectConstructor | { type: ObjectConstructor } ? { [key: string]: any } : T extends Prop ? V : T @@ -64,8 +62,6 @@ type NormalizedProp = type NormalizedPropsOptions = Record -const isReservedKey = (key: string): boolean => key[0] === '_' || key[0] === '$' - // resolve raw VNode data. // - filter out reserved keys (key, ref, slots) // - extract class and style into $attrs (to be merged onto child @@ -182,7 +178,7 @@ function normalizePropsOptions( warn(`props must be strings when using array syntax.`, raw[i]) } const normalizedKey = camelize(raw[i]) - if (!isReservedKey(normalizedKey)) { + if (normalizedKey[0] !== '$') { normalized[normalizedKey] = EMPTY_OBJ } else if (__DEV__) { warn(`Invalid prop name: "${normalizedKey}" is a reserved property.`) @@ -194,7 +190,7 @@ function normalizePropsOptions( } for (const key in raw) { const normalizedKey = camelize(key) - if (!isReservedKey(normalizedKey)) { + if (normalizedKey[0] !== '$') { const opt = raw[key] const prop: NormalizedProp = (normalized[normalizedKey] = isArray(opt) || isFunction(opt) ? { type: opt } : opt) diff --git a/packages/runtime-core/src/createRenderer.ts b/packages/runtime-core/src/createRenderer.ts index 636af115..de0a3d8e 100644 --- a/packages/runtime-core/src/createRenderer.ts +++ b/packages/runtime-core/src/createRenderer.ts @@ -226,7 +226,7 @@ export function createRenderer(options: RendererOptions) { const oldProps = (n1 && n1.props) || EMPTY_OBJ const newProps = n2.props || EMPTY_OBJ - if (patchFlag != null) { + if (patchFlag) { // the presence of a patchFlag means this element's render code was // generated by the compiler and can take the fast path. // in this path old node and new node are guaranteed to have the same shape @@ -239,7 +239,6 @@ export function createRenderer(options: RendererOptions) { // class // this flag is matched when the element has dynamic class bindings. if (patchFlag & CLASS) { - // TODO handle full class API, potentially optimize at compilation stage? if (oldProps.class !== newProps.class) { hostPatchProp(el, 'class', newProps.class, null, false) } @@ -314,6 +313,7 @@ export function createRenderer(options: RendererOptions) { ) { if (oldProps !== newProps) { for (const key in newProps) { + if (key === 'key' || key === 'ref') continue const next = newProps[key] const prev = oldProps[key] if (next !== prev) { @@ -330,6 +330,7 @@ export function createRenderer(options: RendererOptions) { } if (oldProps !== EMPTY_OBJ) { for (const key in oldProps) { + if (key === 'key' || key === 'ref') continue if (!(key in newProps)) { hostPatchProp( el, @@ -521,7 +522,7 @@ export function createRenderer(options: RendererOptions) { // fast path const { patchFlag } = n2 - if (patchFlag != null) { + if (patchFlag) { if (patchFlag & KEYED) { // this could be either fully-keyed or mixed (some keyed some not) // presence of patchFlag means children are guaranteed to be arrays diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index d9fc82fb..ff4ea116 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -2,6 +2,7 @@ import { isArray, isFunction, isString, isObject, EMPTY_ARR } from '@vue/shared' import { ComponentInstance } from './component' import { HostNode } from './createRenderer' import { RawSlots } from './componentSlots' +import { CLASS, STYLE } from './patchFlags' export const Fragment = Symbol('Fragment') export const Text = Symbol('Text') @@ -35,7 +36,7 @@ export interface VNode { target: HostNode | null // portal target // optimization only - patchFlag: number | null + patchFlag: number dynamicProps: string[] | null dynamicChildren: VNode[] | null } @@ -87,12 +88,14 @@ export function createVNode( type: VNodeTypes, props: { [key: string]: any } | null | 0 = null, children: any = null, - patchFlag: number | null = null, + patchFlag: number = 0, dynamicProps: string[] | null = null ): VNode { + // Allow passing 0 for props, this can save bytes on generated code. + props = props || null const vnode: VNode = { type, - props: props || null, + props, key: props && props.key, children: normalizeChildren(children), component: null, @@ -103,16 +106,27 @@ export function createVNode( dynamicProps, dynamicChildren: null } + + // class & style normalization. + if (props !== null) { + // class normalization only needed if the vnode isn't generated by + // compiler-optimized code + if (props.class != null && !(patchFlag & CLASS)) { + props.class = normalizeClass(props.class) + } + if (props.style != null) { + props.style = normalizeStyle(props.style) + } + } + // presence of a patch flag indicates this node is dynamic // component nodes also should always be tracked, because even if the // component doesn't need to update, it needs to persist the instance on to // the next vnode so that it can be properly unmounted later. - if ( - shouldTrack && - (patchFlag != null || isObject(type) || isFunction(type)) - ) { + if (shouldTrack && (patchFlag || isObject(type) || isFunction(type))) { trackDynamicNode(vnode) } + return vnode } @@ -158,3 +172,40 @@ export function normalizeChildren(children: unknown): NormalizedChildren { return isString(children) ? children : children + '' } } + +function normalizeStyle( + value: unknown +): Record | void { + if (isArray(value)) { + const res: Record = {} + for (let i = 0; i < value.length; i++) { + const normalized = normalizeStyle(value[i]) + if (normalized) { + for (const key in normalized) { + res[key] = normalized[key] + } + } + } + return res + } else if (isObject(value)) { + return value + } +} + +function normalizeClass(value: unknown): string { + let res = '' + if (isString(value)) { + res = value + } else if (isArray(value)) { + for (let i = 0; i < value.length; i++) { + res += normalizeClass(value[i]) + ' ' + } + } else if (isObject(value)) { + for (const name in value) { + if (value[name]) { + res += name + ' ' + } + } + } + return res.trim() +}