123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- /* @flow */
- import { warn } from './debug'
- import { observe, toggleObserving, shouldObserve } from '../observer/index'
- import {
- hasOwn,
- isObject,
- toRawType,
- hyphenate,
- capitalize,
- isPlainObject
- } from 'shared/util'
- type PropOptions = {
- type: Function | Array<Function> | null,
- default: any,
- required: ?boolean,
- validator: ?Function
- };
- export function validateProp (
- key: string,
- propOptions: Object,
- propsData: Object,
- vm?: Component
- ): any {
- const prop = propOptions[key]
- const absent = !hasOwn(propsData, key)
- let value = propsData[key]
- // boolean casting
- const booleanIndex = getTypeIndex(Boolean, prop.type)
- if (booleanIndex > -1) {
- if (absent && !hasOwn(prop, 'default')) {
- value = false
- } else if (value === '' || value === hyphenate(key)) {
- // only cast empty string / same name to boolean if
- // boolean has higher priority
- const stringIndex = getTypeIndex(String, prop.type)
- if (stringIndex < 0 || booleanIndex < stringIndex) {
- value = true
- }
- }
- }
- // check default value
- if (value === undefined) {
- value = getPropDefaultValue(vm, prop, key)
- // since the default value is a fresh copy,
- // make sure to observe it.
- const prevShouldObserve = shouldObserve
- toggleObserving(true)
- observe(value)
- toggleObserving(prevShouldObserve)
- }
- if (
- process.env.NODE_ENV !== 'production' &&
- // skip validation for weex recycle-list child component props
- !(__WEEX__ && isObject(value) && ('@binding' in value))
- ) {
- assertProp(prop, key, value, vm, absent)
- }
- return value
- }
- /**
- * Get the default value of a prop.
- */
- function getPropDefaultValue (vm: ?Component, prop: PropOptions, key: string): any {
- // no default, return undefined
- if (!hasOwn(prop, 'default')) {
- return undefined
- }
- const def = prop.default
- // warn against non-factory defaults for Object & Array
- if (process.env.NODE_ENV !== 'production' && isObject(def)) {
- warn(
- 'Invalid default value for prop "' + key + '": ' +
- 'Props with type Object/Array must use a factory function ' +
- 'to return the default value.',
- vm
- )
- }
- // the raw prop value was also undefined from previous render,
- // return previous default value to avoid unnecessary watcher trigger
- if (vm && vm.$options.propsData &&
- vm.$options.propsData[key] === undefined &&
- vm._props[key] !== undefined
- ) {
- return vm._props[key]
- }
- // call factory function for non-Function types
- // a value is Function if its prototype is function even across different execution context
- return typeof def === 'function' && getType(prop.type) !== 'Function'
- ? def.call(vm)
- : def
- }
- /**
- * Assert whether a prop is valid.
- */
- function assertProp (
- prop: PropOptions,
- name: string,
- value: any,
- vm: ?Component,
- absent: boolean
- ) {
- if (prop.required && absent) {
- warn(
- 'Missing required prop: "' + name + '"',
- vm
- )
- return
- }
- if (value == null && !prop.required) {
- return
- }
- let type = prop.type
- let valid = !type || type === true
- const expectedTypes = []
- if (type) {
- if (!Array.isArray(type)) {
- type = [type]
- }
- for (let i = 0; i < type.length && !valid; i++) {
- const assertedType = assertType(value, type[i])
- expectedTypes.push(assertedType.expectedType || '')
- valid = assertedType.valid
- }
- }
- if (!valid) {
- warn(
- getInvalidTypeMessage(name, value, expectedTypes),
- vm
- )
- return
- }
- const validator = prop.validator
- if (validator) {
- if (!validator(value)) {
- warn(
- 'Invalid prop: custom validator check failed for prop "' + name + '".',
- vm
- )
- }
- }
- }
- const simpleCheckRE = /^(String|Number|Boolean|Function|Symbol)$/
- function assertType (value: any, type: Function): {
- valid: boolean;
- expectedType: string;
- } {
- let valid
- const expectedType = getType(type)
- if (simpleCheckRE.test(expectedType)) {
- const t = typeof value
- valid = t === expectedType.toLowerCase()
- // for primitive wrapper objects
- if (!valid && t === 'object') {
- valid = value instanceof type
- }
- } else if (expectedType === 'Object') {
- valid = isPlainObject(value)
- } else if (expectedType === 'Array') {
- valid = Array.isArray(value)
- } else {
- valid = value instanceof type
- }
- return {
- valid,
- expectedType
- }
- }
- /**
- * Use function string name to check built-in types,
- * because a simple equality check will fail when running
- * across different vms / iframes.
- */
- function getType (fn) {
- const match = fn && fn.toString().match(/^\s*function (\w+)/)
- return match ? match[1] : ''
- }
- function isSameType (a, b) {
- return getType(a) === getType(b)
- }
- function getTypeIndex (type, expectedTypes): number {
- if (!Array.isArray(expectedTypes)) {
- return isSameType(expectedTypes, type) ? 0 : -1
- }
- for (let i = 0, len = expectedTypes.length; i < len; i++) {
- if (isSameType(expectedTypes[i], type)) {
- return i
- }
- }
- return -1
- }
- function getInvalidTypeMessage (name, value, expectedTypes) {
- let message = `Invalid prop: type check failed for prop "${name}".` +
- ` Expected ${expectedTypes.map(capitalize).join(', ')}`
- const expectedType = expectedTypes[0]
- const receivedType = toRawType(value)
- const expectedValue = styleValue(value, expectedType)
- const receivedValue = styleValue(value, receivedType)
- // check if we need to specify expected value
- if (expectedTypes.length === 1 &&
- isExplicable(expectedType) &&
- !isBoolean(expectedType, receivedType)) {
- message += ` with value ${expectedValue}`
- }
- message += `, got ${receivedType} `
- // check if we need to specify received value
- if (isExplicable(receivedType)) {
- message += `with value ${receivedValue}.`
- }
- return message
- }
- function styleValue (value, type) {
- if (type === 'String') {
- return `"${value}"`
- } else if (type === 'Number') {
- return `${Number(value)}`
- } else {
- return `${value}`
- }
- }
- function isExplicable (value) {
- const explicitTypes = ['string', 'number', 'boolean']
- return explicitTypes.some(elem => value.toLowerCase() === elem)
- }
- function isBoolean (...args) {
- return args.some(elem => elem.toLowerCase() === 'boolean')
- }
|