From a1b9144009c969a37aed380fb2bd183a6bcb649c Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 24 Sep 2018 19:11:14 -0400 Subject: [PATCH] refactor: new attrs merge strategy --- packages/core/src/component.ts | 2 +- packages/core/src/componentUtils.ts | 9 ++---- packages/core/src/h.ts | 11 ++++++- packages/core/src/utils.ts | 37 ++++++++++++++++++++++ packages/core/src/vdom.ts | 31 +++++++++++++++++- packages/renderer-dom/src/modules/class.ts | 24 ++------------ packages/renderer-dom/src/modules/style.ts | 33 ++----------------- 7 files changed, 87 insertions(+), 60 deletions(-) diff --git a/packages/core/src/component.ts b/packages/core/src/component.ts index 376d28fc6..34ef7e513 100644 --- a/packages/core/src/component.ts +++ b/packages/core/src/component.ts @@ -37,7 +37,7 @@ export interface MountedComponent extends Component { $children: MountedComponent[] $options: ComponentOptions - render(props: P, slots: Slots): any + render(props: P, slots: Slots, attrs: Data): any renderError?(e: Error): any renderTracked?(e: DebuggerEvent): void renderTriggered?(e: DebuggerEvent): void diff --git a/packages/core/src/componentUtils.ts b/packages/core/src/componentUtils.ts index 4ad27fd74..33e54b391 100644 --- a/packages/core/src/componentUtils.ts +++ b/packages/core/src/componentUtils.ts @@ -59,7 +59,8 @@ export function renderInstanceRoot(instance: MountedComponent) { vnode = instance.render.call( instance.$proxy, instance.$props, - instance.$slots + instance.$slots, + instance.$attrs ) } catch (e1) { handleError(e1, instance, ErrorTypes.RENDER) @@ -105,17 +106,13 @@ export function normalizeComponentRoot( vnode = createFragment(vnode) } else { const { flags } = vnode - // parentVNode data merge down if ( componentVNode && (flags & VNodeFlags.COMPONENT || flags & VNodeFlags.ELEMENT) ) { if (inheritAttrs !== false && attrs !== void 0) { - // TODO should merge - console.log(attrs) vnode = cloneVNode(vnode, attrs) - } - if (vnode.el) { + } else if (vnode.el) { vnode = cloneVNode(vnode) } if (flags & VNodeFlags.COMPONENT) { diff --git a/packages/core/src/h.ts b/packages/core/src/h.ts index be913246e..6888207b9 100644 --- a/packages/core/src/h.ts +++ b/packages/core/src/h.ts @@ -9,6 +9,7 @@ import { createFragment, createPortal } from './vdom' +import { isObservable } from '@vue/observer' export const Fragment = Symbol() export const Portal = Symbol() @@ -36,7 +37,15 @@ export const h = ((tag: ElementType, data?: any, children?: any): VNode => { data = null } - // TODO clone data if it is observed + if (data === void 0) data = null + if (children === void 0) children = null + + if (__DEV__ && isObservable(data)) { + console.warn( + `Do not used observed state as VNode data - always create fresh objects.`, + data + ) + } let key = null let ref = null diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index e0605d5d6..7ea9151cd 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -11,6 +11,43 @@ export const isReservedProp = (key: string): boolean => { } } +export function normalizeStyle( + value: any +): Record | void { + if (Array.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 (value && typeof value === 'object') { + return value + } +} + +export function normalizeClass(value: any): string { + let res = '' + if (typeof value === 'string') { + res = value + } else if (Array.isArray(value)) { + for (let i = 0; i < value.length; i++) { + res += normalizeClass(value[i]) + ' ' + } + } else if (typeof value === 'object') { + for (const name in value) { + if (value[name]) { + res += name + ' ' + } + } + } + return res.trim() +} + // https://en.wikipedia.org/wiki/Longest_increasing_subsequence export function lis(arr: number[]): number[] { const p = arr.slice() diff --git a/packages/core/src/vdom.ts b/packages/core/src/vdom.ts index da139d292..c3481d9d9 100644 --- a/packages/core/src/vdom.ts +++ b/packages/core/src/vdom.ts @@ -5,6 +5,7 @@ import { } from './component' import { VNodeFlags, ChildrenFlags } from './flags' import { createComponentClassFromOptions } from './componentUtils' +import { normalizeClass, normalizeStyle } from './utils' // Vue core is platform agnostic, so we are not using Element for "DOM" nodes. export interface RenderNode { @@ -95,6 +96,15 @@ export function createVNode( return vnode } +function normalizeClassAndStyle(data: VNodeData) { + if (data.class != null) { + data.class = normalizeClass(data.class) + } + if (data.style != null) { + data.style = normalizeStyle(data.style) + } +} + export function createElementVNode( tag: string, data: VNodeData | null, @@ -104,6 +114,9 @@ export function createElementVNode( ref?: Ref | null ) { const flags = tag === 'svg' ? VNodeFlags.ELEMENT_SVG : VNodeFlags.ELEMENT_HTML + if (data !== null) { + normalizeClassAndStyle(data) + } return createVNode(flags, tag, data, children, childFlags, key, ref, null) } @@ -175,6 +188,11 @@ export function createComponentVNode( } } + // class & style + if (data !== null) { + normalizeClassAndStyle(data) + } + return createVNode( flags, comp, @@ -248,7 +266,18 @@ export function cloneVNode(vnode: VNode, extraData?: VNodeData): VNode { } } for (const key in extraData) { - clonedData[key] = extraData[key] + if (key === 'class') { + clonedData.class = normalizeClass([clonedData.class, extraData.class]) + } else if (key === 'style') { + clonedData.style = normalizeStyle([clonedData.style, extraData.style]) + } else if (key.startsWith('on')) { + const existing = clonedData[key] + clonedData[key] = existing + ? [].concat(existing, extraData[key]) + : extraData[key] + } else { + clonedData[key] = extraData[key] + } } } return createVNode( diff --git a/packages/renderer-dom/src/modules/class.ts b/packages/renderer-dom/src/modules/class.ts index f953d46f6..ff5b90238 100644 --- a/packages/renderer-dom/src/modules/class.ts +++ b/packages/renderer-dom/src/modules/class.ts @@ -1,29 +1,11 @@ // compiler should normlaize class + :class bindings on the same element // into a single binding ['staticClass', dynamic] -export function patchClass(el: Element, value: any, isSVG: boolean) { +export function patchClass(el: Element, value: string, isSVG: boolean) { // directly setting className should be faster than setAttribute in theory if (isSVG) { - el.setAttribute('class', normalizeClass(value)) + el.setAttribute('class', value) } else { - el.className = normalizeClass(value) + el.className = value } } - -function normalizeClass(value: any): string { - let res = '' - if (typeof value === 'string') { - res = value - } else if (Array.isArray(value)) { - for (let i = 0; i < value.length; i++) { - res += normalizeClass(value[i]) + ' ' - } - } else if (typeof value === 'object') { - for (const name in value) { - if (value[name]) { - res += name + ' ' - } - } - } - return res.trim() -} diff --git a/packages/renderer-dom/src/modules/style.ts b/packages/renderer-dom/src/modules/style.ts index bdf876b7f..f347064b4 100644 --- a/packages/renderer-dom/src/modules/style.ts +++ b/packages/renderer-dom/src/modules/style.ts @@ -1,5 +1,3 @@ -import { isObservable } from '@vue/core' - // style properties that should NOT have "px" added when numeric const nonNumericRE = /acit|ex(?:s|g|n|p|$)|rph|ows|mnc|ntw|ine[ch]|zoo|^ord/i @@ -10,44 +8,19 @@ export function patchStyle(el: any, prev: any, next: any, data: any) { } else if (typeof next === 'string') { style.cssText = next } else { - // TODO: warn invalid value in dev - const normalizedNext: any = normalizeStyle(next) - // If next is observed, the user is likely to mutate the style object. - // We need to replace data.style with the normalized clone. - if (isObservable(next)) { - data.style = normalizedNext - } - for (const key in normalizedNext) { - let value = normalizedNext[key] + for (const key in next) { + let value = next[key] if (typeof value === 'number' && !nonNumericRE.test(key)) { value = value + 'px' } style[key] = value } if (prev && typeof prev !== 'string') { - prev = normalizeStyle(prev) for (const key in prev) { - if (!normalizedNext[key]) { + if (!next[key]) { style[key] = '' } } } } } - -function normalizeStyle(value: any): Record | void { - if (value && typeof value === 'object') { - return value - } else if (Array.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 - } -}