f6b0f45fcb6ef1be8a26774fde3a2b4ef6f1d87d24a5b8fddf273e3d2fb6ec3dbd9248cd2966fc13f29bc2f46a2a8d93010e907ee87d6b8e9a9bf32fb6083a 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. /* @flow */
  2. import { warn } from './debug'
  3. import { observe, toggleObserving, shouldObserve } from '../observer/index'
  4. import {
  5. hasOwn,
  6. isObject,
  7. toRawType,
  8. hyphenate,
  9. capitalize,
  10. isPlainObject
  11. } from 'shared/util'
  12. type PropOptions = {
  13. type: Function | Array<Function> | null,
  14. default: any,
  15. required: ?boolean,
  16. validator: ?Function
  17. };
  18. export function validateProp (
  19. key: string,
  20. propOptions: Object,
  21. propsData: Object,
  22. vm?: Component
  23. ): any {
  24. const prop = propOptions[key]
  25. const absent = !hasOwn(propsData, key)
  26. let value = propsData[key]
  27. // boolean casting
  28. const booleanIndex = getTypeIndex(Boolean, prop.type)
  29. if (booleanIndex > -1) {
  30. if (absent && !hasOwn(prop, 'default')) {
  31. value = false
  32. } else if (value === '' || value === hyphenate(key)) {
  33. // only cast empty string / same name to boolean if
  34. // boolean has higher priority
  35. const stringIndex = getTypeIndex(String, prop.type)
  36. if (stringIndex < 0 || booleanIndex < stringIndex) {
  37. value = true
  38. }
  39. }
  40. }
  41. // check default value
  42. if (value === undefined) {
  43. value = getPropDefaultValue(vm, prop, key)
  44. // since the default value is a fresh copy,
  45. // make sure to observe it.
  46. const prevShouldObserve = shouldObserve
  47. toggleObserving(true)
  48. observe(value)
  49. toggleObserving(prevShouldObserve)
  50. }
  51. if (
  52. process.env.NODE_ENV !== 'production' &&
  53. // skip validation for weex recycle-list child component props
  54. !(__WEEX__ && isObject(value) && ('@binding' in value))
  55. ) {
  56. assertProp(prop, key, value, vm, absent)
  57. }
  58. return value
  59. }
  60. /**
  61. * Get the default value of a prop.
  62. */
  63. function getPropDefaultValue (vm: ?Component, prop: PropOptions, key: string): any {
  64. // no default, return undefined
  65. if (!hasOwn(prop, 'default')) {
  66. return undefined
  67. }
  68. const def = prop.default
  69. // warn against non-factory defaults for Object & Array
  70. if (process.env.NODE_ENV !== 'production' && isObject(def)) {
  71. warn(
  72. 'Invalid default value for prop "' + key + '": ' +
  73. 'Props with type Object/Array must use a factory function ' +
  74. 'to return the default value.',
  75. vm
  76. )
  77. }
  78. // the raw prop value was also undefined from previous render,
  79. // return previous default value to avoid unnecessary watcher trigger
  80. if (vm && vm.$options.propsData &&
  81. vm.$options.propsData[key] === undefined &&
  82. vm._props[key] !== undefined
  83. ) {
  84. return vm._props[key]
  85. }
  86. // call factory function for non-Function types
  87. // a value is Function if its prototype is function even across different execution context
  88. return typeof def === 'function' && getType(prop.type) !== 'Function'
  89. ? def.call(vm)
  90. : def
  91. }
  92. /**
  93. * Assert whether a prop is valid.
  94. */
  95. function assertProp (
  96. prop: PropOptions,
  97. name: string,
  98. value: any,
  99. vm: ?Component,
  100. absent: boolean
  101. ) {
  102. if (prop.required && absent) {
  103. warn(
  104. 'Missing required prop: "' + name + '"',
  105. vm
  106. )
  107. return
  108. }
  109. if (value == null && !prop.required) {
  110. return
  111. }
  112. let type = prop.type
  113. let valid = !type || type === true
  114. const expectedTypes = []
  115. if (type) {
  116. if (!Array.isArray(type)) {
  117. type = [type]
  118. }
  119. for (let i = 0; i < type.length && !valid; i++) {
  120. const assertedType = assertType(value, type[i])
  121. expectedTypes.push(assertedType.expectedType || '')
  122. valid = assertedType.valid
  123. }
  124. }
  125. if (!valid) {
  126. warn(
  127. getInvalidTypeMessage(name, value, expectedTypes),
  128. vm
  129. )
  130. return
  131. }
  132. const validator = prop.validator
  133. if (validator) {
  134. if (!validator(value)) {
  135. warn(
  136. 'Invalid prop: custom validator check failed for prop "' + name + '".',
  137. vm
  138. )
  139. }
  140. }
  141. }
  142. const simpleCheckRE = /^(String|Number|Boolean|Function|Symbol)$/
  143. function assertType (value: any, type: Function): {
  144. valid: boolean;
  145. expectedType: string;
  146. } {
  147. let valid
  148. const expectedType = getType(type)
  149. if (simpleCheckRE.test(expectedType)) {
  150. const t = typeof value
  151. valid = t === expectedType.toLowerCase()
  152. // for primitive wrapper objects
  153. if (!valid && t === 'object') {
  154. valid = value instanceof type
  155. }
  156. } else if (expectedType === 'Object') {
  157. valid = isPlainObject(value)
  158. } else if (expectedType === 'Array') {
  159. valid = Array.isArray(value)
  160. } else {
  161. valid = value instanceof type
  162. }
  163. return {
  164. valid,
  165. expectedType
  166. }
  167. }
  168. /**
  169. * Use function string name to check built-in types,
  170. * because a simple equality check will fail when running
  171. * across different vms / iframes.
  172. */
  173. function getType (fn) {
  174. const match = fn && fn.toString().match(/^\s*function (\w+)/)
  175. return match ? match[1] : ''
  176. }
  177. function isSameType (a, b) {
  178. return getType(a) === getType(b)
  179. }
  180. function getTypeIndex (type, expectedTypes): number {
  181. if (!Array.isArray(expectedTypes)) {
  182. return isSameType(expectedTypes, type) ? 0 : -1
  183. }
  184. for (let i = 0, len = expectedTypes.length; i < len; i++) {
  185. if (isSameType(expectedTypes[i], type)) {
  186. return i
  187. }
  188. }
  189. return -1
  190. }
  191. function getInvalidTypeMessage (name, value, expectedTypes) {
  192. let message = `Invalid prop: type check failed for prop "${name}".` +
  193. ` Expected ${expectedTypes.map(capitalize).join(', ')}`
  194. const expectedType = expectedTypes[0]
  195. const receivedType = toRawType(value)
  196. const expectedValue = styleValue(value, expectedType)
  197. const receivedValue = styleValue(value, receivedType)
  198. // check if we need to specify expected value
  199. if (expectedTypes.length === 1 &&
  200. isExplicable(expectedType) &&
  201. !isBoolean(expectedType, receivedType)) {
  202. message += ` with value ${expectedValue}`
  203. }
  204. message += `, got ${receivedType} `
  205. // check if we need to specify received value
  206. if (isExplicable(receivedType)) {
  207. message += `with value ${receivedValue}.`
  208. }
  209. return message
  210. }
  211. function styleValue (value, type) {
  212. if (type === 'String') {
  213. return `"${value}"`
  214. } else if (type === 'Number') {
  215. return `${Number(value)}`
  216. } else {
  217. return `${value}`
  218. }
  219. }
  220. function isExplicable (value) {
  221. const explicitTypes = ['string', 'number', 'boolean']
  222. return explicitTypes.some(elem => value.toLowerCase() === elem)
  223. }
  224. function isBoolean (...args) {
  225. return args.some(elem => elem.toLowerCase() === 'boolean')
  226. }