refactor: new attrs merge strategy

This commit is contained in:
Evan You 2018-09-24 19:11:14 -04:00
parent 85cd69a988
commit a1b9144009
7 changed files with 87 additions and 60 deletions

View File

@ -37,7 +37,7 @@ export interface MountedComponent<D = Data, P = Data> extends Component {
$children: MountedComponent[] $children: MountedComponent[]
$options: ComponentOptions<D, P> $options: ComponentOptions<D, P>
render(props: P, slots: Slots): any render(props: P, slots: Slots, attrs: Data): any
renderError?(e: Error): any renderError?(e: Error): any
renderTracked?(e: DebuggerEvent): void renderTracked?(e: DebuggerEvent): void
renderTriggered?(e: DebuggerEvent): void renderTriggered?(e: DebuggerEvent): void

View File

@ -59,7 +59,8 @@ export function renderInstanceRoot(instance: MountedComponent) {
vnode = instance.render.call( vnode = instance.render.call(
instance.$proxy, instance.$proxy,
instance.$props, instance.$props,
instance.$slots instance.$slots,
instance.$attrs
) )
} catch (e1) { } catch (e1) {
handleError(e1, instance, ErrorTypes.RENDER) handleError(e1, instance, ErrorTypes.RENDER)
@ -105,17 +106,13 @@ export function normalizeComponentRoot(
vnode = createFragment(vnode) vnode = createFragment(vnode)
} else { } else {
const { flags } = vnode const { flags } = vnode
// parentVNode data merge down
if ( if (
componentVNode && componentVNode &&
(flags & VNodeFlags.COMPONENT || flags & VNodeFlags.ELEMENT) (flags & VNodeFlags.COMPONENT || flags & VNodeFlags.ELEMENT)
) { ) {
if (inheritAttrs !== false && attrs !== void 0) { if (inheritAttrs !== false && attrs !== void 0) {
// TODO should merge
console.log(attrs)
vnode = cloneVNode(vnode, attrs) vnode = cloneVNode(vnode, attrs)
} } else if (vnode.el) {
if (vnode.el) {
vnode = cloneVNode(vnode) vnode = cloneVNode(vnode)
} }
if (flags & VNodeFlags.COMPONENT) { if (flags & VNodeFlags.COMPONENT) {

View File

@ -9,6 +9,7 @@ import {
createFragment, createFragment,
createPortal createPortal
} from './vdom' } from './vdom'
import { isObservable } from '@vue/observer'
export const Fragment = Symbol() export const Fragment = Symbol()
export const Portal = Symbol() export const Portal = Symbol()
@ -36,7 +37,15 @@ export const h = ((tag: ElementType, data?: any, children?: any): VNode => {
data = null 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 key = null
let ref = null let ref = null

View File

@ -11,6 +11,43 @@ export const isReservedProp = (key: string): boolean => {
} }
} }
export function normalizeStyle(
value: any
): Record<string, string | number> | void {
if (Array.isArray(value)) {
const res: Record<string, string | number> = {}
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 // https://en.wikipedia.org/wiki/Longest_increasing_subsequence
export function lis(arr: number[]): number[] { export function lis(arr: number[]): number[] {
const p = arr.slice() const p = arr.slice()

View File

@ -5,6 +5,7 @@ import {
} from './component' } from './component'
import { VNodeFlags, ChildrenFlags } from './flags' import { VNodeFlags, ChildrenFlags } from './flags'
import { createComponentClassFromOptions } from './componentUtils' import { createComponentClassFromOptions } from './componentUtils'
import { normalizeClass, normalizeStyle } from './utils'
// Vue core is platform agnostic, so we are not using Element for "DOM" nodes. // Vue core is platform agnostic, so we are not using Element for "DOM" nodes.
export interface RenderNode { export interface RenderNode {
@ -95,6 +96,15 @@ export function createVNode(
return vnode 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( export function createElementVNode(
tag: string, tag: string,
data: VNodeData | null, data: VNodeData | null,
@ -104,6 +114,9 @@ export function createElementVNode(
ref?: Ref | null ref?: Ref | null
) { ) {
const flags = tag === 'svg' ? VNodeFlags.ELEMENT_SVG : VNodeFlags.ELEMENT_HTML 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) 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( return createVNode(
flags, flags,
comp, comp,
@ -248,7 +266,18 @@ export function cloneVNode(vnode: VNode, extraData?: VNodeData): VNode {
} }
} }
for (const key in extraData) { 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( return createVNode(

View File

@ -1,29 +1,11 @@
// compiler should normlaize class + :class bindings on the same element // compiler should normlaize class + :class bindings on the same element
// into a single binding ['staticClass', dynamic] // 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 // directly setting className should be faster than setAttribute in theory
if (isSVG) { if (isSVG) {
el.setAttribute('class', normalizeClass(value)) el.setAttribute('class', value)
} else { } 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()
}

View File

@ -1,5 +1,3 @@
import { isObservable } from '@vue/core'
// style properties that should NOT have "px" added when numeric // 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 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') { } else if (typeof next === 'string') {
style.cssText = next style.cssText = next
} else { } else {
// TODO: warn invalid value in dev for (const key in next) {
const normalizedNext: any = normalizeStyle(next) let value = next[key]
// 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]
if (typeof value === 'number' && !nonNumericRE.test(key)) { if (typeof value === 'number' && !nonNumericRE.test(key)) {
value = value + 'px' value = value + 'px'
} }
style[key] = value style[key] = value
} }
if (prev && typeof prev !== 'string') { if (prev && typeof prev !== 'string') {
prev = normalizeStyle(prev)
for (const key in prev) { for (const key in prev) {
if (!normalizedNext[key]) { if (!next[key]) {
style[key] = '' style[key] = ''
} }
} }
} }
} }
} }
function normalizeStyle(value: any): Record<string, string | number> | void {
if (value && typeof value === 'object') {
return value
} else if (Array.isArray(value)) {
const res: Record<string, string | number> = {}
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
}
}