refactor: new attrs merge strategy
This commit is contained in:
parent
85cd69a988
commit
a1b9144009
@ -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
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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(
|
||||||
|
@ -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()
|
|
||||||
}
|
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user