componentProps.ts 23 KB

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