]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat: boolean casting
authorEvan You <yyx990803@gmail.com>
Tue, 2 Oct 2018 21:54:58 +0000 (17:54 -0400)
committerEvan You <yyx990803@gmail.com>
Tue, 2 Oct 2018 21:54:58 +0000 (17:54 -0400)
packages/core/src/componentProps.ts
packages/core/src/utils.ts

index 2a77c2049582b3a43347ba7626e15dc9957d4024..0b11c11aca1df360d257b8419468afcda1563bab 100644 (file)
@@ -9,8 +9,11 @@ import {
   Data,
   ComponentPropsOptions,
   PropValidator,
-  PropOptions
+  PropOptions,
+  Prop,
+  PropType
 } from './componentOptions'
+import { camelize, hyphenate } from './utils'
 
 export function initializeProps(instance: MountedComponent, data: Data | null) {
   const { props, attrs } = resolveProps(
@@ -75,10 +78,10 @@ export function resolveProps(
   Component: ComponentClass | FunctionalComponent
 ): { props: Data; attrs?: Data } {
   const hasDeclaredProps = rawOptions !== void 0
-  const options = rawOptions as ComponentPropsOptions
   if (!rawData && !hasDeclaredProps) {
     return EMPTY_PROPS
   }
+  const options = normalizePropsOptions(rawOptions) as NormalizedPropsOptions
   const props: any = {}
   let attrs: any = void 0
   if (rawData != null) {
@@ -102,29 +105,126 @@ export function resolveProps(
         const newKey = isNativeOn ? 'on' + key.slice(8) : key
         ;(attrs || (attrs = {}))[newKey] = rawData[key]
       } else {
-        if (__DEV__ && hasDeclaredProps && options.hasOwnProperty(key)) {
-          validateProp(key, rawData[key], options[key], Component)
-        }
         props[key] = rawData[key]
       }
     }
   }
-  // set default values
+  // set default values, cast booleans & run validators
   if (hasDeclaredProps) {
     for (const key in options) {
-      if (props[key] === void 0) {
-        const opt = options[key]
-        if (opt != null && opt.hasOwnProperty('default')) {
-          const defaultValue = (opt as PropOptions).default
-          props[key] =
-            typeof defaultValue === 'function' ? defaultValue() : defaultValue
+      let opt = options[key]
+      if (opt == null) continue
+      const isAbsent = !props.hasOwnProperty(key)
+      const hasDefault = opt.hasOwnProperty('default')
+      const currentValue = props[key]
+      // 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 }
 }
 
+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(
   key: string,
   value: any,
index 60052e70f66769ce06820240add0bdb239ed4ee0..13d81d2d4fbdf2c5daa562bb5b348547542b79e1 100644 (file)
@@ -45,6 +45,16 @@ export function normalizeClass(value: any): string {
   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
 export function lis(arr: number[]): number[] {
   const p = arr.slice()