componentProps.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783
  1. import {
  2. toRaw,
  3. shallowReactive,
  4. trigger,
  5. TriggerOpTypes
  6. } from '@vue/reactivity'
  7. import {
  8. EMPTY_OBJ,
  9. camelize,
  10. hyphenate,
  11. capitalize,
  12. isString,
  13. isFunction,
  14. isArray,
  15. isObject,
  16. hasOwn,
  17. toRawType,
  18. PatchFlags,
  19. makeMap,
  20. isReservedProp,
  21. EMPTY_ARR,
  22. def,
  23. extend,
  24. isOn,
  25. IfAny
  26. } from '@vue/shared'
  27. import { warn } from './warning'
  28. import {
  29. Data,
  30. ComponentInternalInstance,
  31. ComponentOptions,
  32. ConcreteComponent,
  33. setCurrentInstance,
  34. unsetCurrentInstance
  35. } from './component'
  36. import { isEmitListener } from './componentEmits'
  37. import { InternalObjectKey } from './vnode'
  38. import { AppContext } from './apiCreateApp'
  39. import { createPropsDefaultThis } from './compat/props'
  40. import { isCompatEnabled, softAssertCompatEnabled } from './compat/compatConfig'
  41. import { DeprecationTypes } from './compat/compatConfig'
  42. import { shouldSkipAttr } from './compat/attrsFallthrough'
  43. export type ComponentPropsOptions<P = Data> =
  44. | ComponentObjectPropsOptions<P>
  45. | string[]
  46. export type ComponentObjectPropsOptions<P = Data> = {
  47. [K in keyof P]: Prop<P[K]> | null
  48. }
  49. export type Prop<T, D = T> = PropOptions<T, D> | PropType<T>
  50. type DefaultFactory<T> = (props: Data) => T | null | undefined
  51. export interface PropOptions<T = any, D = T> {
  52. type?: PropType<T> | true | null
  53. required?: boolean
  54. default?: D | DefaultFactory<D> | null | undefined | object
  55. validator?(value: unknown): boolean
  56. /**
  57. * @internal
  58. */
  59. skipCheck?: boolean
  60. /**
  61. * @internal
  62. */
  63. skipFactory?: boolean
  64. }
  65. export type PropType<T> = PropConstructor<T> | PropConstructor<T>[]
  66. type PropConstructor<T = any> =
  67. | { new (...args: any[]): T & {} }
  68. | { (): T }
  69. | PropMethod<T>
  70. type PropMethod<T, TConstructor = any> = [T] extends [
  71. ((...args: any) => any) | undefined
  72. ] // if is function with args, allowing non-required functions
  73. ? { new (): TConstructor; (): T; readonly prototype: TConstructor } // Create Function like constructor
  74. : never
  75. type RequiredKeys<T> = {
  76. [K in keyof T]: T[K] extends
  77. | { required: true }
  78. | { default: any }
  79. // don't mark Boolean props as undefined
  80. | BooleanConstructor
  81. | { type: BooleanConstructor }
  82. ? T[K] extends { default: undefined | (() => undefined) }
  83. ? never
  84. : K
  85. : never
  86. }[keyof T]
  87. type OptionalKeys<T> = Exclude<keyof T, RequiredKeys<T>>
  88. type DefaultKeys<T> = {
  89. [K in keyof T]: T[K] extends
  90. | { default: any }
  91. // Boolean implicitly defaults to false
  92. | BooleanConstructor
  93. | { type: BooleanConstructor }
  94. ? T[K] extends { type: BooleanConstructor; required: true } // not default if Boolean is marked as required
  95. ? never
  96. : K
  97. : never
  98. }[keyof T]
  99. type InferPropType<T> = [T] extends [null]
  100. ? any // null & true would fail to infer
  101. : [T] extends [{ type: null | true }]
  102. ? any // As TS issue https://github.com/Microsoft/TypeScript/issues/14829 // somehow `ObjectConstructor` when inferred from { (): T } becomes `any` // `BooleanConstructor` when inferred from PropConstructor(with PropMethod) becomes `Boolean`
  103. : [T] extends [ObjectConstructor | { type: ObjectConstructor }]
  104. ? Record<string, any>
  105. : [T] extends [BooleanConstructor | { type: BooleanConstructor }]
  106. ? boolean
  107. : [T] extends [DateConstructor | { type: DateConstructor }]
  108. ? Date
  109. : [T] extends [(infer U)[] | { type: (infer U)[] }]
  110. ? U extends DateConstructor
  111. ? Date | InferPropType<U>
  112. : InferPropType<U>
  113. : [T] extends [Prop<infer V, infer D>]
  114. ? unknown extends V
  115. ? IfAny<V, V, D>
  116. : V
  117. : T
  118. /**
  119. * Extract prop types from a runtime props options object.
  120. * The extracted types are **internal** - i.e. the resolved props received by
  121. * the component.
  122. * - Boolean props are always present
  123. * - Props with default values are always present
  124. *
  125. * To extract accepted props from the parent, use {@link ExtractPublicPropTypes}.
  126. */
  127. export type ExtractPropTypes<O> = {
  128. // use `keyof Pick<O, RequiredKeys<O>>` instead of `RequiredKeys<O>` to
  129. // support IDE features
  130. [K in keyof Pick<O, RequiredKeys<O>>]: InferPropType<O[K]>
  131. } & {
  132. // use `keyof Pick<O, OptionalKeys<O>>` instead of `OptionalKeys<O>` to
  133. // support IDE features
  134. [K in keyof Pick<O, OptionalKeys<O>>]?: InferPropType<O[K]>
  135. }
  136. type PublicRequiredKeys<T> = {
  137. [K in keyof T]: T[K] extends { required: true } ? K : never
  138. }[keyof T]
  139. type PublicOptionalKeys<T> = Exclude<keyof T, PublicRequiredKeys<T>>
  140. /**
  141. * Extract prop types from a runtime props options object.
  142. * The extracted types are **public** - i.e. the expected props that can be
  143. * passed to component.
  144. */
  145. export type ExtractPublicPropTypes<O> = {
  146. [K in keyof Pick<O, PublicRequiredKeys<O>>]: InferPropType<O[K]>
  147. } & {
  148. [K in keyof Pick<O, PublicOptionalKeys<O>>]?: InferPropType<O[K]>
  149. }
  150. const enum BooleanFlags {
  151. shouldCast,
  152. shouldCastTrue
  153. }
  154. // extract props which defined with default from prop options
  155. export type ExtractDefaultPropTypes<O> = O extends object
  156. ? // use `keyof Pick<O, DefaultKeys<O>>` instead of `DefaultKeys<O>` to support IDE features
  157. { [K in keyof Pick<O, DefaultKeys<O>>]: InferPropType<O[K]> }
  158. : {}
  159. type NormalizedProp =
  160. | null
  161. | (PropOptions & {
  162. [BooleanFlags.shouldCast]?: boolean
  163. [BooleanFlags.shouldCastTrue]?: boolean
  164. })
  165. // normalized value is a tuple of the actual normalized options
  166. // and an array of prop keys that need value casting (booleans and defaults)
  167. export type NormalizedProps = Record<string, NormalizedProp>
  168. export type NormalizedPropsOptions = [NormalizedProps, string[]] | []
  169. export function initProps(
  170. instance: ComponentInternalInstance,
  171. rawProps: Data | null,
  172. isStateful: number, // result of bitwise flag comparison
  173. isSSR = false
  174. ) {
  175. const props: Data = {}
  176. const attrs: Data = {}
  177. def(attrs, InternalObjectKey, 1)
  178. instance.propsDefaults = Object.create(null)
  179. setFullProps(instance, rawProps, props, attrs)
  180. // ensure all declared prop keys are present
  181. for (const key in instance.propsOptions[0]) {
  182. if (!(key in props)) {
  183. props[key] = undefined
  184. }
  185. }
  186. // validation
  187. if (__DEV__) {
  188. validateProps(rawProps || {}, props, instance)
  189. }
  190. if (isStateful) {
  191. // stateful
  192. instance.props = isSSR ? props : shallowReactive(props)
  193. } else {
  194. if (!instance.type.props) {
  195. // functional w/ optional props, props === attrs
  196. instance.props = attrs
  197. } else {
  198. // functional w/ declared props
  199. instance.props = props
  200. }
  201. }
  202. instance.attrs = attrs
  203. }
  204. function isInHmrContext(instance: ComponentInternalInstance | null) {
  205. while (instance) {
  206. if (instance.type.__hmrId) return true
  207. instance = instance.parent
  208. }
  209. }
  210. export function updateProps(
  211. instance: ComponentInternalInstance,
  212. rawProps: Data | null,
  213. rawPrevProps: Data | null,
  214. optimized: boolean
  215. ) {
  216. const {
  217. props,
  218. attrs,
  219. vnode: { patchFlag }
  220. } = instance
  221. const rawCurrentProps = toRaw(props)
  222. const [options] = instance.propsOptions
  223. let hasAttrsChanged = false
  224. if (
  225. // always force full diff in dev
  226. // - #1942 if hmr is enabled with sfc component
  227. // - vite#872 non-sfc component used by sfc component
  228. !(__DEV__ && isInHmrContext(instance)) &&
  229. (optimized || patchFlag > 0) &&
  230. !(patchFlag & PatchFlags.FULL_PROPS)
  231. ) {
  232. if (patchFlag & PatchFlags.PROPS) {
  233. // Compiler-generated props & no keys change, just set the updated
  234. // the props.
  235. const propsToUpdate = instance.vnode.dynamicProps!
  236. for (let i = 0; i < propsToUpdate.length; i++) {
  237. let key = propsToUpdate[i]
  238. // skip if the prop key is a declared emit event listener
  239. if (isEmitListener(instance.emitsOptions, key)) {
  240. continue
  241. }
  242. // PROPS flag guarantees rawProps to be non-null
  243. const value = rawProps![key]
  244. if (options) {
  245. // attr / props separation was done on init and will be consistent
  246. // in this code path, so just check if attrs have it.
  247. if (hasOwn(attrs, key)) {
  248. if (value !== attrs[key]) {
  249. attrs[key] = value
  250. hasAttrsChanged = true
  251. }
  252. } else {
  253. const camelizedKey = camelize(key)
  254. props[camelizedKey] = resolvePropValue(
  255. options,
  256. rawCurrentProps,
  257. camelizedKey,
  258. value,
  259. instance,
  260. false /* isAbsent */
  261. )
  262. }
  263. } else {
  264. if (__COMPAT__) {
  265. if (isOn(key) && key.endsWith('Native')) {
  266. key = key.slice(0, -6) // remove Native postfix
  267. } else if (shouldSkipAttr(key, instance)) {
  268. continue
  269. }
  270. }
  271. if (value !== attrs[key]) {
  272. attrs[key] = value
  273. hasAttrsChanged = true
  274. }
  275. }
  276. }
  277. }
  278. } else {
  279. // full props update.
  280. if (setFullProps(instance, rawProps, props, attrs)) {
  281. hasAttrsChanged = true
  282. }
  283. // in case of dynamic props, check if we need to delete keys from
  284. // the props object
  285. let kebabKey: string
  286. for (const key in rawCurrentProps) {
  287. if (
  288. !rawProps ||
  289. // for camelCase
  290. (!hasOwn(rawProps, key) &&
  291. // it's possible the original props was passed in as kebab-case
  292. // and converted to camelCase (#955)
  293. ((kebabKey = hyphenate(key)) === key || !hasOwn(rawProps, kebabKey)))
  294. ) {
  295. if (options) {
  296. if (
  297. rawPrevProps &&
  298. // for camelCase
  299. (rawPrevProps[key] !== undefined ||
  300. // for kebab-case
  301. rawPrevProps[kebabKey!] !== undefined)
  302. ) {
  303. props[key] = resolvePropValue(
  304. options,
  305. rawCurrentProps,
  306. key,
  307. undefined,
  308. instance,
  309. true /* isAbsent */
  310. )
  311. }
  312. } else {
  313. delete props[key]
  314. }
  315. }
  316. }
  317. // in the case of functional component w/o props declaration, props and
  318. // attrs point to the same object so it should already have been updated.
  319. if (attrs !== rawCurrentProps) {
  320. for (const key in attrs) {
  321. if (
  322. !rawProps ||
  323. (!hasOwn(rawProps, key) &&
  324. (!__COMPAT__ || !hasOwn(rawProps, key + 'Native')))
  325. ) {
  326. delete attrs[key]
  327. hasAttrsChanged = true
  328. }
  329. }
  330. }
  331. }
  332. // trigger updates for $attrs in case it's used in component slots
  333. if (hasAttrsChanged) {
  334. trigger(instance, TriggerOpTypes.SET, '$attrs')
  335. }
  336. if (__DEV__) {
  337. validateProps(rawProps || {}, props, instance)
  338. }
  339. }
  340. function setFullProps(
  341. instance: ComponentInternalInstance,
  342. rawProps: Data | null,
  343. props: Data,
  344. attrs: Data
  345. ) {
  346. const [options, needCastKeys] = instance.propsOptions
  347. let hasAttrsChanged = false
  348. let rawCastValues: Data | undefined
  349. if (rawProps) {
  350. for (let key in rawProps) {
  351. // key, ref are reserved and never passed down
  352. if (isReservedProp(key)) {
  353. continue
  354. }
  355. if (__COMPAT__) {
  356. if (key.startsWith('onHook:')) {
  357. softAssertCompatEnabled(
  358. DeprecationTypes.INSTANCE_EVENT_HOOKS,
  359. instance,
  360. key.slice(2).toLowerCase()
  361. )
  362. }
  363. if (key === 'inline-template') {
  364. continue
  365. }
  366. }
  367. const value = rawProps[key]
  368. // prop option names are camelized during normalization, so to support
  369. // kebab -> camel conversion here we need to camelize the key.
  370. let camelKey
  371. if (options && hasOwn(options, (camelKey = camelize(key)))) {
  372. if (!needCastKeys || !needCastKeys.includes(camelKey)) {
  373. props[camelKey] = value
  374. } else {
  375. ;(rawCastValues || (rawCastValues = {}))[camelKey] = value
  376. }
  377. } else if (!isEmitListener(instance.emitsOptions, key)) {
  378. // Any non-declared (either as a prop or an emitted event) props are put
  379. // into a separate `attrs` object for spreading. Make sure to preserve
  380. // original key casing
  381. if (__COMPAT__) {
  382. if (isOn(key) && key.endsWith('Native')) {
  383. key = key.slice(0, -6) // remove Native postfix
  384. } else if (shouldSkipAttr(key, instance)) {
  385. continue
  386. }
  387. }
  388. if (!(key in attrs) || value !== attrs[key]) {
  389. attrs[key] = value
  390. hasAttrsChanged = true
  391. }
  392. }
  393. }
  394. }
  395. if (needCastKeys) {
  396. const rawCurrentProps = toRaw(props)
  397. const castValues = rawCastValues || EMPTY_OBJ
  398. for (let i = 0; i < needCastKeys.length; i++) {
  399. const key = needCastKeys[i]
  400. props[key] = resolvePropValue(
  401. options!,
  402. rawCurrentProps,
  403. key,
  404. castValues[key],
  405. instance,
  406. !hasOwn(castValues, key)
  407. )
  408. }
  409. }
  410. return hasAttrsChanged
  411. }
  412. function resolvePropValue(
  413. options: NormalizedProps,
  414. props: Data,
  415. key: string,
  416. value: unknown,
  417. instance: ComponentInternalInstance,
  418. isAbsent: boolean
  419. ) {
  420. const opt = options[key]
  421. if (opt != null) {
  422. const hasDefault = hasOwn(opt, 'default')
  423. // default values
  424. if (hasDefault && value === undefined) {
  425. const defaultValue = opt.default
  426. if (
  427. opt.type !== Function &&
  428. !opt.skipFactory &&
  429. isFunction(defaultValue)
  430. ) {
  431. const { propsDefaults } = instance
  432. if (key in propsDefaults) {
  433. value = propsDefaults[key]
  434. } else {
  435. setCurrentInstance(instance)
  436. value = propsDefaults[key] = defaultValue.call(
  437. __COMPAT__ &&
  438. isCompatEnabled(DeprecationTypes.PROPS_DEFAULT_THIS, instance)
  439. ? createPropsDefaultThis(instance, props, key)
  440. : null,
  441. props
  442. )
  443. unsetCurrentInstance()
  444. }
  445. } else {
  446. value = defaultValue
  447. }
  448. }
  449. // boolean casting
  450. if (opt[BooleanFlags.shouldCast]) {
  451. if (isAbsent && !hasDefault) {
  452. value = false
  453. } else if (
  454. opt[BooleanFlags.shouldCastTrue] &&
  455. (value === '' || value === hyphenate(key))
  456. ) {
  457. value = true
  458. }
  459. }
  460. }
  461. return value
  462. }
  463. export function normalizePropsOptions(
  464. comp: ConcreteComponent,
  465. appContext: AppContext,
  466. asMixin = false
  467. ): NormalizedPropsOptions {
  468. const cache = appContext.propsCache
  469. const cached = cache.get(comp)
  470. if (cached) {
  471. return cached
  472. }
  473. const raw = comp.props
  474. const normalized: NormalizedPropsOptions[0] = {}
  475. const needCastKeys: NormalizedPropsOptions[1] = []
  476. // apply mixin/extends props
  477. let hasExtends = false
  478. if (__FEATURE_OPTIONS_API__ && !isFunction(comp)) {
  479. const extendProps = (raw: ComponentOptions) => {
  480. if (__COMPAT__ && isFunction(raw)) {
  481. raw = raw.options
  482. }
  483. hasExtends = true
  484. const [props, keys] = normalizePropsOptions(raw, appContext, true)
  485. extend(normalized, props)
  486. if (keys) needCastKeys.push(...keys)
  487. }
  488. if (!asMixin && appContext.mixins.length) {
  489. appContext.mixins.forEach(extendProps)
  490. }
  491. if (comp.extends) {
  492. extendProps(comp.extends)
  493. }
  494. if (comp.mixins) {
  495. comp.mixins.forEach(extendProps)
  496. }
  497. }
  498. if (!raw && !hasExtends) {
  499. if (isObject(comp)) {
  500. cache.set(comp, EMPTY_ARR as any)
  501. }
  502. return EMPTY_ARR as any
  503. }
  504. if (isArray(raw)) {
  505. for (let i = 0; i < raw.length; i++) {
  506. if (__DEV__ && !isString(raw[i])) {
  507. warn(`props must be strings when using array syntax.`, raw[i])
  508. }
  509. const normalizedKey = camelize(raw[i])
  510. if (validatePropName(normalizedKey)) {
  511. normalized[normalizedKey] = EMPTY_OBJ
  512. }
  513. }
  514. } else if (raw) {
  515. if (__DEV__ && !isObject(raw)) {
  516. warn(`invalid props options`, raw)
  517. }
  518. for (const key in raw) {
  519. const normalizedKey = camelize(key)
  520. if (validatePropName(normalizedKey)) {
  521. const opt = raw[key]
  522. const prop: NormalizedProp = (normalized[normalizedKey] =
  523. isArray(opt) || isFunction(opt) ? { type: opt } : extend({}, opt))
  524. if (prop) {
  525. const booleanIndex = getTypeIndex(Boolean, prop.type)
  526. const stringIndex = getTypeIndex(String, prop.type)
  527. prop[BooleanFlags.shouldCast] = booleanIndex > -1
  528. prop[BooleanFlags.shouldCastTrue] =
  529. stringIndex < 0 || booleanIndex < stringIndex
  530. // if the prop needs boolean casting or default value
  531. if (booleanIndex > -1 || hasOwn(prop, 'default')) {
  532. needCastKeys.push(normalizedKey)
  533. }
  534. }
  535. }
  536. }
  537. }
  538. const res: NormalizedPropsOptions = [normalized, needCastKeys]
  539. if (isObject(comp)) {
  540. cache.set(comp, res)
  541. }
  542. return res
  543. }
  544. function validatePropName(key: string) {
  545. if (key[0] !== '$') {
  546. return true
  547. } else if (__DEV__) {
  548. warn(`Invalid prop name: "${key}" is a reserved property.`)
  549. }
  550. return false
  551. }
  552. // use function string name to check type constructors
  553. // so that it works across vms / iframes.
  554. function getType(ctor: Prop<any>): string {
  555. const match = ctor && ctor.toString().match(/^\s*(function|class) (\w+)/)
  556. return match ? match[2] : ctor === null ? 'null' : ''
  557. }
  558. function isSameType(a: Prop<any>, b: Prop<any>): boolean {
  559. return getType(a) === getType(b)
  560. }
  561. function getTypeIndex(
  562. type: Prop<any>,
  563. expectedTypes: PropType<any> | void | null | true
  564. ): number {
  565. if (isArray(expectedTypes)) {
  566. return expectedTypes.findIndex(t => isSameType(t, type))
  567. } else if (isFunction(expectedTypes)) {
  568. return isSameType(expectedTypes, type) ? 0 : -1
  569. }
  570. return -1
  571. }
  572. /**
  573. * dev only
  574. */
  575. function validateProps(
  576. rawProps: Data,
  577. props: Data,
  578. instance: ComponentInternalInstance
  579. ) {
  580. const resolvedValues = toRaw(props)
  581. const options = instance.propsOptions[0]
  582. for (const key in options) {
  583. let opt = options[key]
  584. if (opt == null) continue
  585. validateProp(
  586. key,
  587. resolvedValues[key],
  588. opt,
  589. !hasOwn(rawProps, key) && !hasOwn(rawProps, hyphenate(key))
  590. )
  591. }
  592. }
  593. /**
  594. * dev only
  595. */
  596. function validateProp(
  597. name: string,
  598. value: unknown,
  599. prop: PropOptions,
  600. isAbsent: boolean
  601. ) {
  602. const { type, required, validator, skipCheck } = prop
  603. // required!
  604. if (required && isAbsent) {
  605. warn('Missing required prop: "' + name + '"')
  606. return
  607. }
  608. // missing but optional
  609. if (value == null && !required) {
  610. return
  611. }
  612. // type check
  613. if (type != null && type !== true && !skipCheck) {
  614. let isValid = false
  615. const types = isArray(type) ? type : [type]
  616. const expectedTypes = []
  617. // value is valid as long as one of the specified types match
  618. for (let i = 0; i < types.length && !isValid; i++) {
  619. const { valid, expectedType } = assertType(value, types[i])
  620. expectedTypes.push(expectedType || '')
  621. isValid = valid
  622. }
  623. if (!isValid) {
  624. warn(getInvalidTypeMessage(name, value, expectedTypes))
  625. return
  626. }
  627. }
  628. // custom validator
  629. if (validator && !validator(value)) {
  630. warn('Invalid prop: custom validator check failed for prop "' + name + '".')
  631. }
  632. }
  633. const isSimpleType = /*#__PURE__*/ makeMap(
  634. 'String,Number,Boolean,Function,Symbol,BigInt'
  635. )
  636. type AssertionResult = {
  637. valid: boolean
  638. expectedType: string
  639. }
  640. /**
  641. * dev only
  642. */
  643. function assertType(value: unknown, type: PropConstructor): AssertionResult {
  644. let valid
  645. const expectedType = getType(type)
  646. if (isSimpleType(expectedType)) {
  647. const t = typeof value
  648. valid = t === expectedType.toLowerCase()
  649. // for primitive wrapper objects
  650. if (!valid && t === 'object') {
  651. valid = value instanceof type
  652. }
  653. } else if (expectedType === 'Object') {
  654. valid = isObject(value)
  655. } else if (expectedType === 'Array') {
  656. valid = isArray(value)
  657. } else if (expectedType === 'null') {
  658. valid = value === null
  659. } else {
  660. valid = value instanceof type
  661. }
  662. return {
  663. valid,
  664. expectedType
  665. }
  666. }
  667. /**
  668. * dev only
  669. */
  670. function getInvalidTypeMessage(
  671. name: string,
  672. value: unknown,
  673. expectedTypes: string[]
  674. ): string {
  675. if (expectedTypes.length === 0) {
  676. return (
  677. `Prop type [] for prop "${name}" won't match anything.` +
  678. ` Did you mean to use type Array instead?`
  679. )
  680. }
  681. let message =
  682. `Invalid prop: type check failed for prop "${name}".` +
  683. ` Expected ${expectedTypes.map(capitalize).join(' | ')}`
  684. const expectedType = expectedTypes[0]
  685. const receivedType = toRawType(value)
  686. const expectedValue = styleValue(value, expectedType)
  687. const receivedValue = styleValue(value, receivedType)
  688. // check if we need to specify expected value
  689. if (
  690. expectedTypes.length === 1 &&
  691. isExplicable(expectedType) &&
  692. !isBoolean(expectedType, receivedType)
  693. ) {
  694. message += ` with value ${expectedValue}`
  695. }
  696. message += `, got ${receivedType} `
  697. // check if we need to specify received value
  698. if (isExplicable(receivedType)) {
  699. message += `with value ${receivedValue}.`
  700. }
  701. return message
  702. }
  703. /**
  704. * dev only
  705. */
  706. function styleValue(value: unknown, type: string): string {
  707. if (type === 'String') {
  708. return `"${value}"`
  709. } else if (type === 'Number') {
  710. return `${Number(value)}`
  711. } else {
  712. return `${value}`
  713. }
  714. }
  715. /**
  716. * dev only
  717. */
  718. function isExplicable(type: string): boolean {
  719. const explicitTypes = ['string', 'number', 'boolean']
  720. return explicitTypes.some(elem => type.toLowerCase() === elem)
  721. }
  722. /**
  723. * dev only
  724. */
  725. function isBoolean(...args: string[]): boolean {
  726. return args.some(elem => elem.toLowerCase() === 'boolean')
  727. }