wip: style/class normalization

This commit is contained in:
Evan You 2019-06-01 17:43:41 +08:00
parent 05556eacb2
commit d6d4ba8679
3 changed files with 68 additions and 20 deletions

View File

@ -36,12 +36,10 @@ type RequiredKeys<T> = {
type OptionalKeys<T> = Exclude<keyof T, RequiredKeys<T>>
type InferPropType<T> = 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<infer V> ? V : T
@ -64,8 +62,6 @@ type NormalizedProp =
type NormalizedPropsOptions = Record<string, NormalizedProp>
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)

View File

@ -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

View File

@ -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<string, string | number> | void {
if (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 (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()
}