componentProps.ts 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815
  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. makeMap,
  25. toRawType,
  26. } from '@vue/shared'
  27. import type { Data } from '@vue/runtime-shared'
  28. import { warn } from './warning'
  29. import {
  30. type ComponentInternalInstance,
  31. type ComponentOptions,
  32. type ConcreteComponent,
  33. setCurrentInstance,
  34. } from './component'
  35. import { isEmitListener } from './componentEmits'
  36. import type { AppContext } from './apiCreateApp'
  37. import { createPropsDefaultThis } from './compat/props'
  38. import { isCompatEnabled, softAssertCompatEnabled } from './compat/compatConfig'
  39. import { DeprecationTypes } from './compat/compatConfig'
  40. import { shouldSkipAttr } from './compat/attrsFallthrough'
  41. import { createInternalObject } from './internalObject'
  42. export type ComponentPropsOptions<P = Data> =
  43. | ComponentObjectPropsOptions<P>
  44. | string[]
  45. export type ComponentObjectPropsOptions<P = Data> = {
  46. [K in keyof P]: Prop<P[K]> | null
  47. }
  48. export type Prop<T, D = T> = PropOptions<T, D> | PropType<T>
  49. type DefaultFactory<T> = (props: Data) => T | null | undefined
  50. export interface PropOptions<T = any, D = T> {
  51. type?: PropType<T> | true | null
  52. required?: boolean
  53. default?: D | DefaultFactory<D> | null | undefined | object
  54. validator?(value: unknown, props: Data): boolean
  55. /**
  56. * @internal
  57. */
  58. skipCheck?: boolean
  59. /**
  60. * @internal
  61. */
  62. skipFactory?: boolean
  63. }
  64. export type PropType<T> = PropConstructor<T> | (PropConstructor<T> | null)[]
  65. type PropConstructor<T = any> =
  66. | { new (...args: any[]): T & {} }
  67. | { (): T }
  68. | PropMethod<T>
  69. type PropMethod<T, TConstructor = any> = [T] extends [
  70. ((...args: any) => any) | undefined,
  71. ] // if is function with args, allowing non-required functions
  72. ? { new (): TConstructor; (): T; readonly prototype: TConstructor } // Create Function like constructor
  73. : never
  74. type RequiredKeys<T> = {
  75. [K in keyof T]: T[K] extends
  76. | { required: true }
  77. | { default: any }
  78. // don't mark Boolean props as undefined
  79. | BooleanConstructor
  80. | { type: BooleanConstructor }
  81. ? T[K] extends { default: undefined | (() => undefined) }
  82. ? never
  83. : K
  84. : never
  85. }[keyof T]
  86. type OptionalKeys<T> = Exclude<keyof T, RequiredKeys<T>>
  87. type DefaultKeys<T> = {
  88. [K in keyof T]: T[K] extends
  89. | { default: any }
  90. // Boolean implicitly defaults to false
  91. | BooleanConstructor
  92. | { type: BooleanConstructor }
  93. ? T[K] extends { type: BooleanConstructor; required: true } // not default if Boolean is marked as required
  94. ? never
  95. : K
  96. : never
  97. }[keyof T]
  98. type InferPropType<T, NullAsAny = true> = [T] extends [null]
  99. ? NullAsAny extends true
  100. ? any
  101. : null
  102. : [T] extends [{ type: null | true }]
  103. ? 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`
  104. : [T] extends [ObjectConstructor | { type: ObjectConstructor }]
  105. ? Record<string, any>
  106. : [T] extends [BooleanConstructor | { type: BooleanConstructor }]
  107. ? boolean
  108. : [T] extends [DateConstructor | { type: DateConstructor }]
  109. ? Date
  110. : [T] extends [(infer U)[] | { type: (infer U)[] }]
  111. ? U extends DateConstructor
  112. ? Date | InferPropType<U, false>
  113. : InferPropType<U, false>
  114. : [T] extends [Prop<infer V, infer D>]
  115. ? unknown extends V
  116. ? keyof V extends never
  117. ? IfAny<V, V, D>
  118. : V
  119. : V
  120. : T
  121. /**
  122. * Extract prop types from a runtime props options object.
  123. * The extracted types are **internal** - i.e. the resolved props received by
  124. * the component.
  125. * - Boolean props are always present
  126. * - Props with default values are always present
  127. *
  128. * To extract accepted props from the parent, use {@link ExtractPublicPropTypes}.
  129. */
  130. export type ExtractPropTypes<O> = {
  131. // use `keyof Pick<O, RequiredKeys<O>>` instead of `RequiredKeys<O>` to
  132. // support IDE features
  133. [K in keyof Pick<O, RequiredKeys<O>>]: InferPropType<O[K]>
  134. } & {
  135. // use `keyof Pick<O, OptionalKeys<O>>` instead of `OptionalKeys<O>` to
  136. // support IDE features
  137. [K in keyof Pick<O, OptionalKeys<O>>]?: InferPropType<O[K]>
  138. }
  139. type PublicRequiredKeys<T> = {
  140. [K in keyof T]: T[K] extends { required: true } ? K : never
  141. }[keyof T]
  142. type PublicOptionalKeys<T> = Exclude<keyof T, PublicRequiredKeys<T>>
  143. /**
  144. * Extract prop types from a runtime props options object.
  145. * The extracted types are **public** - i.e. the expected props that can be
  146. * passed to component.
  147. */
  148. export type ExtractPublicPropTypes<O> = {
  149. [K in keyof Pick<O, PublicRequiredKeys<O>>]: InferPropType<O[K]>
  150. } & {
  151. [K in keyof Pick<O, PublicOptionalKeys<O>>]?: InferPropType<O[K]>
  152. }
  153. enum BooleanFlags {
  154. shouldCast,
  155. shouldCastTrue,
  156. }
  157. // extract props which defined with default from prop options
  158. export type ExtractDefaultPropTypes<O> = O extends object
  159. ? // use `keyof Pick<O, DefaultKeys<O>>` instead of `DefaultKeys<O>` to support IDE features
  160. { [K in keyof Pick<O, DefaultKeys<O>>]: InferPropType<O[K]> }
  161. : {}
  162. type NormalizedProp = PropOptions & {
  163. [BooleanFlags.shouldCast]?: boolean
  164. [BooleanFlags.shouldCastTrue]?: boolean
  165. }
  166. // normalized value is a tuple of the actual normalized options
  167. // and an array of prop keys that need value casting (booleans and defaults)
  168. export type NormalizedProps = Record<string, NormalizedProp>
  169. export type NormalizedPropsOptions = [NormalizedProps, string[]] | []
  170. export function initProps(
  171. instance: ComponentInternalInstance,
  172. rawProps: Data | null,
  173. isStateful: number, // result of bitwise flag comparison
  174. isSSR = false,
  175. ): void {
  176. const props: Data = {}
  177. const attrs: Data = createInternalObject()
  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. ): void {
  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.attrs, TriggerOpTypes.SET, '')
  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. const reset = 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. reset()
  444. }
  445. } else {
  446. value = defaultValue
  447. }
  448. // #9006 reflect default value on custom element
  449. if (instance.ce) {
  450. instance.ce._setProp(key, value)
  451. }
  452. }
  453. // boolean casting
  454. if (opt[BooleanFlags.shouldCast]) {
  455. if (isAbsent && !hasDefault) {
  456. value = false
  457. } else if (
  458. opt[BooleanFlags.shouldCastTrue] &&
  459. (value === '' || value === hyphenate(key))
  460. ) {
  461. value = true
  462. }
  463. }
  464. }
  465. return value
  466. }
  467. const mixinPropsCache = new WeakMap<ConcreteComponent, NormalizedPropsOptions>()
  468. export function normalizePropsOptions(
  469. comp: ConcreteComponent,
  470. appContext: AppContext,
  471. asMixin = false,
  472. ): NormalizedPropsOptions {
  473. const cache =
  474. __FEATURE_OPTIONS_API__ && asMixin ? mixinPropsCache : appContext.propsCache
  475. const cached = cache.get(comp)
  476. if (cached) {
  477. return cached
  478. }
  479. const raw = comp.props
  480. const normalized: NormalizedPropsOptions[0] = {}
  481. const needCastKeys: NormalizedPropsOptions[1] = []
  482. // apply mixin/extends props
  483. let hasExtends = false
  484. if (__FEATURE_OPTIONS_API__ && !isFunction(comp)) {
  485. const extendProps = (raw: ComponentOptions) => {
  486. if (__COMPAT__ && isFunction(raw)) {
  487. raw = raw.options
  488. }
  489. hasExtends = true
  490. const [props, keys] = normalizePropsOptions(raw, appContext, true)
  491. extend(normalized, props)
  492. if (keys) needCastKeys.push(...keys)
  493. }
  494. if (!asMixin && appContext.mixins.length) {
  495. appContext.mixins.forEach(extendProps)
  496. }
  497. if (comp.extends) {
  498. extendProps(comp.extends)
  499. }
  500. if (comp.mixins) {
  501. comp.mixins.forEach(extendProps)
  502. }
  503. }
  504. if (!raw && !hasExtends) {
  505. if (isObject(comp)) {
  506. cache.set(comp, EMPTY_ARR as any)
  507. }
  508. return EMPTY_ARR as any
  509. }
  510. if (isArray(raw)) {
  511. for (let i = 0; i < raw.length; i++) {
  512. if (__DEV__ && !isString(raw[i])) {
  513. warn(`props must be strings when using array syntax.`, raw[i])
  514. }
  515. const normalizedKey = camelize(raw[i])
  516. if (validatePropName(normalizedKey)) {
  517. normalized[normalizedKey] = EMPTY_OBJ
  518. }
  519. }
  520. } else if (raw) {
  521. if (__DEV__ && !isObject(raw)) {
  522. warn(`invalid props options`, raw)
  523. }
  524. for (const key in raw) {
  525. const normalizedKey = camelize(key)
  526. if (validatePropName(normalizedKey)) {
  527. const opt = raw[key]
  528. const prop: NormalizedProp = (normalized[normalizedKey] =
  529. isArray(opt) || isFunction(opt) ? { type: opt } : extend({}, opt))
  530. const propType = prop.type
  531. let shouldCast = false
  532. let shouldCastTrue = true
  533. if (isArray(propType)) {
  534. for (let index = 0; index < propType.length; ++index) {
  535. const type = propType[index]
  536. const typeName = isFunction(type) && type.name
  537. if (typeName === 'Boolean') {
  538. shouldCast = true
  539. break
  540. } else if (typeName === 'String') {
  541. // If we find `String` before `Boolean`, e.g. `[String, Boolean]`,
  542. // we need to handle the casting slightly differently. Props
  543. // passed as `<Comp checked="">` or `<Comp checked="checked">`
  544. // will either be treated as strings or converted to a boolean
  545. // `true`, depending on the order of the types.
  546. shouldCastTrue = false
  547. }
  548. }
  549. } else {
  550. shouldCast = isFunction(propType) && propType.name === 'Boolean'
  551. }
  552. prop[BooleanFlags.shouldCast] = shouldCast
  553. prop[BooleanFlags.shouldCastTrue] = shouldCastTrue
  554. // if the prop needs boolean casting or default value
  555. if (shouldCast || hasOwn(prop, 'default')) {
  556. needCastKeys.push(normalizedKey)
  557. }
  558. }
  559. }
  560. }
  561. const res: NormalizedPropsOptions = [normalized, needCastKeys]
  562. if (isObject(comp)) {
  563. cache.set(comp, res)
  564. }
  565. return res
  566. }
  567. function validatePropName(key: string) {
  568. if (key[0] !== '$' && !isReservedProp(key)) {
  569. return true
  570. } else if (__DEV__) {
  571. warn(`Invalid prop name: "${key}" is a reserved property.`)
  572. }
  573. return false
  574. }
  575. // dev only
  576. // use function string name to check type constructors
  577. // so that it works across vms / iframes.
  578. function getType(ctor: Prop<any> | null): string {
  579. // Early return for null to avoid unnecessary computations
  580. if (ctor === null) {
  581. return 'null'
  582. }
  583. // Avoid using regex for common cases by checking the type directly
  584. if (typeof ctor === 'function') {
  585. // Using name property to avoid converting function to string
  586. return ctor.name || ''
  587. } else if (typeof ctor === 'object') {
  588. // Attempting to directly access constructor name if possible
  589. const name = ctor.constructor && ctor.constructor.name
  590. return name || ''
  591. }
  592. // Fallback for other types (though they're less likely to have meaningful names here)
  593. return ''
  594. }
  595. /**
  596. * dev only
  597. */
  598. function validateProps(
  599. rawProps: Data,
  600. props: Data,
  601. instance: ComponentInternalInstance,
  602. ) {
  603. const resolvedValues = toRaw(props)
  604. const options = instance.propsOptions[0]
  605. for (const key in options) {
  606. let opt = options[key]
  607. if (opt == null) continue
  608. validateProp(
  609. key,
  610. resolvedValues[key],
  611. opt,
  612. __DEV__ ? shallowReadonly(resolvedValues) : resolvedValues,
  613. !hasOwn(rawProps, key) && !hasOwn(rawProps, hyphenate(key)),
  614. )
  615. }
  616. }
  617. /**
  618. * dev only
  619. */
  620. function validateProp(
  621. name: string,
  622. value: unknown,
  623. prop: PropOptions,
  624. props: Data,
  625. isAbsent: boolean,
  626. ) {
  627. const { type, required, validator, skipCheck } = prop
  628. // required!
  629. if (required && isAbsent) {
  630. warn('Missing required prop: "' + name + '"')
  631. return
  632. }
  633. // missing but optional
  634. if (value == null && !required) {
  635. return
  636. }
  637. // type check
  638. if (type != null && type !== true && !skipCheck) {
  639. let isValid = false
  640. const types = isArray(type) ? type : [type]
  641. const expectedTypes = []
  642. // value is valid as long as one of the specified types match
  643. for (let i = 0; i < types.length && !isValid; i++) {
  644. const { valid, expectedType } = assertType(value, types[i])
  645. expectedTypes.push(expectedType || '')
  646. isValid = valid
  647. }
  648. if (!isValid) {
  649. warn(getInvalidTypeMessage(name, value, expectedTypes))
  650. return
  651. }
  652. }
  653. // custom validator
  654. if (validator && !validator(value, props)) {
  655. warn('Invalid prop: custom validator check failed for prop "' + name + '".')
  656. }
  657. }
  658. const isSimpleType = /*@__PURE__*/ makeMap(
  659. 'String,Number,Boolean,Function,Symbol,BigInt',
  660. )
  661. type AssertionResult = {
  662. valid: boolean
  663. expectedType: string
  664. }
  665. /**
  666. * dev only
  667. */
  668. function assertType(
  669. value: unknown,
  670. type: PropConstructor | null,
  671. ): AssertionResult {
  672. let valid
  673. const expectedType = getType(type)
  674. if (expectedType === 'null') {
  675. valid = value === null
  676. } else if (isSimpleType(expectedType)) {
  677. const t = typeof value
  678. valid = t === expectedType.toLowerCase()
  679. // for primitive wrapper objects
  680. if (!valid && t === 'object') {
  681. valid = value instanceof (type as PropConstructor)
  682. }
  683. } else if (expectedType === 'Object') {
  684. valid = isObject(value)
  685. } else if (expectedType === 'Array') {
  686. valid = isArray(value)
  687. } else {
  688. valid = value instanceof (type as PropConstructor)
  689. }
  690. return {
  691. valid,
  692. expectedType,
  693. }
  694. }
  695. /**
  696. * dev only
  697. */
  698. function getInvalidTypeMessage(
  699. name: string,
  700. value: unknown,
  701. expectedTypes: string[],
  702. ): string {
  703. if (expectedTypes.length === 0) {
  704. return (
  705. `Prop type [] for prop "${name}" won't match anything.` +
  706. ` Did you mean to use type Array instead?`
  707. )
  708. }
  709. let message =
  710. `Invalid prop: type check failed for prop "${name}".` +
  711. ` Expected ${expectedTypes.map(capitalize).join(' | ')}`
  712. const expectedType = expectedTypes[0]
  713. const receivedType = toRawType(value)
  714. const expectedValue = styleValue(value, expectedType)
  715. const receivedValue = styleValue(value, receivedType)
  716. // check if we need to specify expected value
  717. if (
  718. expectedTypes.length === 1 &&
  719. isExplicable(expectedType) &&
  720. !isBoolean(expectedType, receivedType)
  721. ) {
  722. message += ` with value ${expectedValue}`
  723. }
  724. message += `, got ${receivedType} `
  725. // check if we need to specify received value
  726. if (isExplicable(receivedType)) {
  727. message += `with value ${receivedValue}.`
  728. }
  729. return message
  730. }
  731. /**
  732. * dev only
  733. */
  734. function styleValue(value: unknown, type: string): string {
  735. if (type === 'String') {
  736. return `"${value}"`
  737. } else if (type === 'Number') {
  738. return `${Number(value)}`
  739. } else {
  740. return `${value}`
  741. }
  742. }
  743. /**
  744. * dev only
  745. */
  746. function isExplicable(type: string): boolean {
  747. const explicitTypes = ['string', 'number', 'boolean']
  748. return explicitTypes.some(elem => type.toLowerCase() === elem)
  749. }
  750. /**
  751. * dev only
  752. */
  753. function isBoolean(...args: string[]): boolean {
  754. return args.some(elem => elem.toLowerCase() === 'boolean')
  755. }