feat: boolean casting
This commit is contained in:
parent
8817443545
commit
e93e85bb29
@ -9,8 +9,11 @@ import {
|
|||||||
Data,
|
Data,
|
||||||
ComponentPropsOptions,
|
ComponentPropsOptions,
|
||||||
PropValidator,
|
PropValidator,
|
||||||
PropOptions
|
PropOptions,
|
||||||
|
Prop,
|
||||||
|
PropType
|
||||||
} from './componentOptions'
|
} from './componentOptions'
|
||||||
|
import { camelize, hyphenate } from './utils'
|
||||||
|
|
||||||
export function initializeProps(instance: MountedComponent, data: Data | null) {
|
export function initializeProps(instance: MountedComponent, data: Data | null) {
|
||||||
const { props, attrs } = resolveProps(
|
const { props, attrs } = resolveProps(
|
||||||
@ -75,10 +78,10 @@ export function resolveProps(
|
|||||||
Component: ComponentClass | FunctionalComponent
|
Component: ComponentClass | FunctionalComponent
|
||||||
): { props: Data; attrs?: Data } {
|
): { props: Data; attrs?: Data } {
|
||||||
const hasDeclaredProps = rawOptions !== void 0
|
const hasDeclaredProps = rawOptions !== void 0
|
||||||
const options = rawOptions as ComponentPropsOptions
|
|
||||||
if (!rawData && !hasDeclaredProps) {
|
if (!rawData && !hasDeclaredProps) {
|
||||||
return EMPTY_PROPS
|
return EMPTY_PROPS
|
||||||
}
|
}
|
||||||
|
const options = normalizePropsOptions(rawOptions) as NormalizedPropsOptions
|
||||||
const props: any = {}
|
const props: any = {}
|
||||||
let attrs: any = void 0
|
let attrs: any = void 0
|
||||||
if (rawData != null) {
|
if (rawData != null) {
|
||||||
@ -102,29 +105,126 @@ export function resolveProps(
|
|||||||
const newKey = isNativeOn ? 'on' + key.slice(8) : key
|
const newKey = isNativeOn ? 'on' + key.slice(8) : key
|
||||||
;(attrs || (attrs = {}))[newKey] = rawData[key]
|
;(attrs || (attrs = {}))[newKey] = rawData[key]
|
||||||
} else {
|
} else {
|
||||||
if (__DEV__ && hasDeclaredProps && options.hasOwnProperty(key)) {
|
|
||||||
validateProp(key, rawData[key], options[key], Component)
|
|
||||||
}
|
|
||||||
props[key] = rawData[key]
|
props[key] = rawData[key]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// set default values
|
// set default values, cast booleans & run validators
|
||||||
if (hasDeclaredProps) {
|
if (hasDeclaredProps) {
|
||||||
for (const key in options) {
|
for (const key in options) {
|
||||||
if (props[key] === void 0) {
|
let opt = options[key]
|
||||||
const opt = options[key]
|
if (opt == null) continue
|
||||||
if (opt != null && opt.hasOwnProperty('default')) {
|
const isAbsent = !props.hasOwnProperty(key)
|
||||||
const defaultValue = (opt as PropOptions).default
|
const hasDefault = opt.hasOwnProperty('default')
|
||||||
props[key] =
|
const currentValue = props[key]
|
||||||
typeof defaultValue === 'function' ? defaultValue() : defaultValue
|
// default values
|
||||||
|
if (hasDefault && currentValue === void 0) {
|
||||||
|
const defaultValue = opt.default
|
||||||
|
props[key] =
|
||||||
|
typeof defaultValue === 'function' ? defaultValue() : defaultValue
|
||||||
|
}
|
||||||
|
// boolean casting
|
||||||
|
if (opt[BooleanFlags.shouldCast]) {
|
||||||
|
if (isAbsent && !hasDefault) {
|
||||||
|
props[key] = false
|
||||||
|
} else if (
|
||||||
|
opt[BooleanFlags.shouldCastTrue] &&
|
||||||
|
(currentValue === '' || currentValue === hyphenate(key))
|
||||||
|
) {
|
||||||
|
props[key] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// runtime validation
|
||||||
|
if (__DEV__) {
|
||||||
|
validateProp(key, rawData[key], opt, Component)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { props, attrs }
|
return { props, attrs }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const enum BooleanFlags {
|
||||||
|
shouldCast = '1',
|
||||||
|
shouldCastTrue = '2'
|
||||||
|
}
|
||||||
|
|
||||||
|
type NormalizedProp = PropOptions & {
|
||||||
|
[BooleanFlags.shouldCast]?: boolean
|
||||||
|
[BooleanFlags.shouldCastTrue]?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type NormalizedPropsOptions = Record<string, NormalizedProp>
|
||||||
|
|
||||||
|
const normalizationCache = new WeakMap<
|
||||||
|
ComponentPropsOptions,
|
||||||
|
NormalizedPropsOptions
|
||||||
|
>()
|
||||||
|
|
||||||
|
function normalizePropsOptions(
|
||||||
|
raw: ComponentPropsOptions | void
|
||||||
|
): NormalizedPropsOptions {
|
||||||
|
if (!raw) {
|
||||||
|
return EMPTY_OBJ
|
||||||
|
}
|
||||||
|
const hit = normalizationCache.get(raw)
|
||||||
|
if (hit) {
|
||||||
|
return hit
|
||||||
|
}
|
||||||
|
const normalized: NormalizedPropsOptions = {}
|
||||||
|
if (Array.isArray(raw)) {
|
||||||
|
for (let i = 0; i < raw.length; i++) {
|
||||||
|
if (__DEV__ && typeof raw !== 'string') {
|
||||||
|
console.warn(`props must be strings when using array syntax.`)
|
||||||
|
}
|
||||||
|
normalized[camelize(raw[i])] = EMPTY_OBJ
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (__DEV__ && typeof raw !== 'object') {
|
||||||
|
console.warn(`invalid props options: `, raw)
|
||||||
|
}
|
||||||
|
for (const key in raw) {
|
||||||
|
const opt = raw[key]
|
||||||
|
const prop = (normalized[camelize(key)] =
|
||||||
|
Array.isArray(opt) || typeof opt === 'function'
|
||||||
|
? { type: opt }
|
||||||
|
: opt) as NormalizedProp
|
||||||
|
const booleanIndex = getTypeIndex(Boolean, prop.type)
|
||||||
|
const stringIndex = getTypeIndex(String, prop.type)
|
||||||
|
prop[BooleanFlags.shouldCast] = booleanIndex > -1
|
||||||
|
prop[BooleanFlags.shouldCastTrue] = booleanIndex < stringIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
normalizationCache.set(raw, normalized)
|
||||||
|
return normalized
|
||||||
|
}
|
||||||
|
|
||||||
|
// use function string name to check type constructors
|
||||||
|
// so that it works across vms / iframes.
|
||||||
|
function getType(ctor: Prop<any>): string {
|
||||||
|
const match = ctor && ctor.toString().match(/^\s*function (\w+)/)
|
||||||
|
return match ? match[1] : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSameType(a: Prop<any>, b: Prop<any>): boolean {
|
||||||
|
return getType(a) === getType(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTypeIndex(
|
||||||
|
type: Prop<any>,
|
||||||
|
expectedTypes: PropType<any> | void
|
||||||
|
): number {
|
||||||
|
if (Array.isArray(expectedTypes)) {
|
||||||
|
for (let i = 0, len = expectedTypes.length; i < len; i++) {
|
||||||
|
if (isSameType(expectedTypes[i], type)) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (expectedTypes != null) {
|
||||||
|
return isSameType(expectedTypes, type) ? 0 : -1
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
function validateProp(
|
function validateProp(
|
||||||
key: string,
|
key: string,
|
||||||
value: any,
|
value: any,
|
||||||
|
@ -45,6 +45,16 @@ export function normalizeClass(value: any): string {
|
|||||||
return res.trim()
|
return res.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const camelizeRE = /-(\w)/g
|
||||||
|
export const camelize = (str: string): string => {
|
||||||
|
return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''))
|
||||||
|
}
|
||||||
|
|
||||||
|
const hyphenateRE = /\B([A-Z])/g
|
||||||
|
export const hyphenate = (str: string): string => {
|
||||||
|
return str.replace(hyphenateRE, '-$1').toLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
// 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()
|
||||||
|
Loading…
Reference in New Issue
Block a user