perf: optimize props resolution

Store the keys for props that need default or boolean casting
during normalization, so that later we only need to iterate
through this array instead of the entire props object.
This commit is contained in:
Evan You 2019-12-12 22:07:48 -05:00
parent 4e91b1328d
commit 2c3c65772b

View File

@ -80,7 +80,9 @@ type NormalizedProp =
[BooleanFlags.shouldCastTrue]?: boolean
})
type NormalizedPropsOptions = Record<string, NormalizedProp>
// normalized value is a tuple of the actual normalized options
// and an array of prop keys that need value casting (booleans and defaults)
type NormalizedPropsOptions = [Record<string, NormalizedProp>, string[]]
// resolve raw VNode data.
// - filter out reserved keys (key, ref, slots)
@ -96,11 +98,11 @@ export function resolveProps(
_options: ComponentPropsOptions | void
) {
const hasDeclaredProps = _options != null
const options = normalizePropsOptions(_options)!
if (!rawProps && !hasDeclaredProps) {
return
}
const { 0: options, 1: needCastKeys } = normalizePropsOptions(_options)!
const props: Data = {}
let attrs: Data | undefined = void 0
@ -135,9 +137,10 @@ export function resolveProps(
}
}
}
// set default values, cast booleans & run validators
if (hasDeclaredProps) {
for (const key in options) {
// set default values & cast booleans
for (let i = 0; i < needCastKeys.length; i++) {
const key = needCastKeys[i]
let opt = options[key]
if (opt == null) continue
const isAbsent = !hasOwn(props, key)
@ -159,15 +162,19 @@ export function resolveProps(
setProp(key, true)
}
}
// runtime validation
}
// validation
if (__DEV__ && rawProps) {
for (const key in options) {
let opt = options[key]
if (opt == null) continue
let rawValue
if (!(key in rawProps) && hyphenate(key) in rawProps) {
rawValue = rawProps[hyphenate(key)]
} else {
rawValue = rawProps[key]
}
validateProp(key, toRaw(rawValue), opt, isAbsent)
validateProp(key, toRaw(rawValue), opt, !hasOwn(props, key))
}
}
} else {
@ -197,19 +204,22 @@ export function resolveProps(
instance.attrs = options ? attrs || EMPTY_OBJ : props
}
const normalizationMap = new WeakMap()
const normalizationMap = new WeakMap<
ComponentPropsOptions,
NormalizedPropsOptions
>()
function normalizePropsOptions(
raw: ComponentPropsOptions | void
): NormalizedPropsOptions | null {
): NormalizedPropsOptions {
if (!raw) {
return null
return [] as any
}
if (normalizationMap.has(raw)) {
return normalizationMap.get(raw)
return normalizationMap.get(raw)!
}
const normalized: NormalizedPropsOptions = {}
normalizationMap.set(raw, normalized)
const options: NormalizedPropsOptions[0] = {}
const needCastKeys: NormalizedPropsOptions[1] = []
if (isArray(raw)) {
for (let i = 0; i < raw.length; i++) {
if (__DEV__ && !isString(raw[i])) {
@ -217,7 +227,7 @@ function normalizePropsOptions(
}
const normalizedKey = camelize(raw[i])
if (normalizedKey[0] !== '$') {
normalized[normalizedKey] = EMPTY_OBJ
options[normalizedKey] = EMPTY_OBJ
} else if (__DEV__) {
warn(`Invalid prop name: "${normalizedKey}" is a reserved property.`)
}
@ -230,19 +240,25 @@ function normalizePropsOptions(
const normalizedKey = camelize(key)
if (normalizedKey[0] !== '$') {
const opt = raw[key]
const prop: NormalizedProp = (normalized[normalizedKey] =
const prop: NormalizedProp = (options[normalizedKey] =
isArray(opt) || isFunction(opt) ? { type: opt } : opt)
if (prop != null) {
const booleanIndex = getTypeIndex(Boolean, prop.type)
const stringIndex = getTypeIndex(String, prop.type)
prop[BooleanFlags.shouldCast] = booleanIndex > -1
prop[BooleanFlags.shouldCastTrue] = booleanIndex < stringIndex
// if the prop needs boolean casting or default value
if (booleanIndex > -1 || hasOwn(prop, 'default')) {
needCastKeys.push(normalizedKey)
}
}
} else if (__DEV__) {
warn(`Invalid prop name: "${normalizedKey}" is a reserved property.`)
}
}
}
const normalized: NormalizedPropsOptions = [options, needCastKeys]
normalizationMap.set(raw, normalized)
return normalized
}