2020-06-13 04:59:15 +08:00
import {
TriggerOpTypes ,
shallowReactive ,
2023-12-05 17:14:17 +08:00
shallowReadonly ,
2023-12-26 19:39:47 +08:00
toRaw ,
2020-06-13 04:59:15 +08:00
trigger ,
} from '@vue/reactivity'
2019-05-28 18:06:00 +08:00
import {
2020-04-07 05:57:27 +08:00
EMPTY_ARR ,
2019-05-28 18:06:00 +08:00
EMPTY_OBJ ,
2023-12-26 19:39:47 +08:00
type IfAny ,
2019-10-23 22:34:58 +08:00
PatchFlags ,
2019-05-28 18:06:00 +08:00
camelize ,
capitalize ,
2021-05-01 03:50:32 +08:00
extend ,
2023-12-26 19:39:47 +08:00
hasOwn ,
2019-05-28 18:06:00 +08:00
hyphenate ,
2023-12-26 19:39:47 +08:00
isArray ,
2019-05-28 18:06:00 +08:00
isFunction ,
2019-06-03 13:44:45 +08:00
isObject ,
2022-01-16 15:43:19 +08:00
isOn ,
isReservedProp ,
2019-05-28 18:06:00 +08:00
isString ,
2023-12-26 19:39:47 +08:00
makeMap ,
2019-10-25 23:15:04 +08:00
toRawType ,
2019-05-28 18:06:00 +08:00
} from '@vue/shared'
import { warn } from './warning'
2020-06-09 23:27:40 +08:00
import {
type ComponentInternalInstance ,
type ComponentOptions ,
2020-09-18 03:59:01 +08:00
type ConcreteComponent ,
2024-12-04 13:50:54 +08:00
type Data ,
2024-12-03 21:43:18 +08:00
type GenericComponentInstance ,
2021-07-07 21:07:19 +08:00
setCurrentInstance ,
2020-06-09 23:27:40 +08:00
} from './component'
2020-04-04 08:40:34 +08:00
import { isEmitListener } from './componentEmits'
2020-09-01 06:32:07 +08:00
import type { AppContext } from './apiCreateApp'
2021-04-06 23:08:21 +08:00
import { createPropsDefaultThis } from './compat/props'
2021-04-13 06:47:50 +08:00
import { isCompatEnabled , softAssertCompatEnabled } from './compat/compatConfig'
import { DeprecationTypes } from './compat/compatConfig'
2021-04-22 10:04:26 +08:00
import { shouldSkipAttr } from './compat/attrsFallthrough'
2024-04-16 22:47:24 +08:00
import { createInternalObject } from './internalObject'
2019-05-28 18:06:00 +08:00
2019-10-08 21:26:09 +08:00
export type ComponentPropsOptions < P = Data > =
| ComponentObjectPropsOptions < P >
| string [ ]
export type ComponentObjectPropsOptions < P = Data > = {
2019-06-01 00:47:05 +08:00
[ K in keyof P ] : Prop < P [ K ] > | null
2019-05-28 18:06:00 +08:00
}
2020-08-18 23:23:18 +08:00
export type Prop < T , D = T > = PropOptions < T , D > | PropType < T >
2019-05-28 18:06:00 +08:00
2020-08-18 23:37:34 +08:00
type DefaultFactory < T > = ( props : Data ) = > T | null | undefined
2019-10-30 23:11:23 +08:00
2021-06-27 09:11:57 +08:00
export interface PropOptions < T = any , D = T > {
2019-05-28 18:06:00 +08:00
type ? : PropType < T > | true | null
required? : boolean
2020-08-20 04:36:42 +08:00
default ? : D | DefaultFactory < D > | null | undefined | object
2023-12-05 17:14:17 +08:00
validator ? ( value : unknown , props : Data ) : boolean
2023-03-29 22:21:27 +08:00
/ * *
* @internal
* /
2023-03-28 22:29:54 +08:00
skipCheck? : boolean
2023-03-29 22:21:27 +08:00
/ * *
* @internal
* /
skipFactory? : boolean
2019-05-31 18:07:43 +08:00
}
2024-04-27 11:48:37 +08:00
export type PropType < T > = PropConstructor < T > | ( PropConstructor < T > | null ) [ ]
2019-06-01 00:47:05 +08:00
2020-04-03 21:28:13 +08:00
type PropConstructor < T = any > =
2021-02-25 05:20:41 +08:00
| { new ( . . . args : any [ ] ) : T & { } }
2020-04-03 21:28:13 +08:00
| { ( ) : T }
| PropMethod < T >
2021-08-24 07:04:03 +08:00
type PropMethod < T , TConstructor = any > = [ T ] extends [
( ( . . . args : any ) = > any ) | undefined ,
] // if is function with args, allowing non-required functions
2020-06-12 05:34:21 +08:00
? { new ( ) : TConstructor ; ( ) : T ; readonly prototype : TConstructor } // Create Function like constructor
2020-04-03 21:28:13 +08:00
: never
2019-06-01 00:47:05 +08:00
2020-09-16 22:09:35 +08:00
type RequiredKeys < T > = {
2020-10-20 05:25:55 +08:00
[ K in keyof T ] : T [ K ] extends
| { required : true }
| { default : any }
// don't mark Boolean props as undefined
| BooleanConstructor
| { type : BooleanConstructor }
2021-07-20 06:24:18 +08:00
? T [ K ] extends { default : undefined | ( ( ) = > undefined ) }
? never
: K
2020-10-20 05:25:55 +08:00
: never
2019-06-01 00:47:05 +08:00
} [ keyof T ]
2020-09-16 22:09:35 +08:00
type OptionalKeys < T > = Exclude < keyof T , RequiredKeys < T > >
type DefaultKeys < T > = {
2020-10-20 05:25:55 +08:00
[ K in keyof T ] : T [ K ] extends
| { default : any }
// Boolean implicitly defaults to false
| BooleanConstructor
| { type : BooleanConstructor }
? T [ K ] extends { type : BooleanConstructor ; required : true } // not default if Boolean is marked as required
? never
: K
: never
2020-09-16 22:09:35 +08:00
} [ keyof T ]
2019-06-01 00:47:05 +08:00
2024-04-27 11:48:37 +08:00
type InferPropType < T , NullAsAny = true > = [ T ] extends [ null ]
? NullAsAny extends true
? any
: null
2021-03-25 23:27:54 +08:00
: [ T ] extends [ { type : null | true } ]
2023-11-18 10:33:24 +08:00
? 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`
: [ T ] extends [ ObjectConstructor | { type : ObjectConstructor } ]
? Record < string , any >
: [ T ] extends [ BooleanConstructor | { type : BooleanConstructor } ]
? boolean
: [ T ] extends [ DateConstructor | { type : DateConstructor } ]
? Date
: [ T ] extends [ ( infer U ) [ ] | { type : ( infer U ) [ ] } ]
? U extends DateConstructor
2024-04-27 11:48:37 +08:00
? Date | InferPropType < U , false >
: InferPropType < U , false >
2023-11-18 10:33:24 +08:00
: [ T ] extends [ Prop < infer V , infer D > ]
? unknown extends V
2024-09-20 20:36:57 +08:00
? keyof V extends never
? IfAny < V , V , D >
: V
2023-11-18 10:33:24 +08:00
: V
: T
2019-06-01 00:47:05 +08:00
2023-05-02 11:06:24 +08:00
/ * *
* Extract prop types from a runtime props options object .
* The extracted types are * * internal * * - i . e . the resolved props received by
* the component .
* - Boolean props are always present
* - Props with default values are always present
*
* To extract accepted props from the parent , use { @link ExtractPublicPropTypes } .
* /
2021-12-25 15:52:22 +08:00
export type ExtractPropTypes < O > = {
2023-05-02 11:06:24 +08:00
// use `keyof Pick<O, RequiredKeys<O>>` instead of `RequiredKeys<O>` to
// support IDE features
2021-12-25 15:52:22 +08:00
[ K in keyof Pick < O , RequiredKeys < O > > ] : InferPropType < O [ K ] >
} & {
2023-05-02 11:06:24 +08:00
// use `keyof Pick<O, OptionalKeys<O>>` instead of `OptionalKeys<O>` to
// support IDE features
2021-12-25 15:52:22 +08:00
[ K in keyof Pick < O , OptionalKeys < O > > ] ? : InferPropType < O [ K ] >
}
2019-05-28 18:06:00 +08:00
2023-05-02 11:06:24 +08:00
type PublicRequiredKeys < T > = {
[ K in keyof T ] : T [ K ] extends { required : true } ? K : never
} [ keyof T ]
type PublicOptionalKeys < T > = Exclude < keyof T , PublicRequiredKeys < T > >
/ * *
* Extract prop types from a runtime props options object .
* The extracted types are * * public * * - i . e . the expected props that can be
* passed to component .
* /
export type ExtractPublicPropTypes < O > = {
[ K in keyof Pick < O , PublicRequiredKeys < O > > ] : InferPropType < O [ K ] >
} & {
[ K in keyof Pick < O , PublicOptionalKeys < O > > ] ? : InferPropType < O [ K ] >
}
2023-11-29 12:24:50 +08:00
enum BooleanFlags {
2019-11-25 05:00:46 +08:00
shouldCast ,
shouldCastTrue ,
2019-05-28 18:06:00 +08:00
}
2020-09-16 22:09:35 +08:00
// extract props which defined with default from prop options
export type ExtractDefaultPropTypes < O > = O extends object
2022-05-13 07:10:00 +08:00
? // use `keyof Pick<O, DefaultKeys<O>>` instead of `DefaultKeys<O>` to support IDE features
{ [ K in keyof Pick < O , DefaultKeys < O > > ] : InferPropType < O [ K ] > }
2020-09-16 22:09:35 +08:00
: { }
2024-07-29 21:47:23 +08:00
type NormalizedProp = PropOptions & {
[ BooleanFlags . shouldCast ] ? : boolean
[ BooleanFlags . shouldCastTrue ] ? : boolean
}
2019-05-28 18:06:00 +08:00
2024-12-03 21:43:18 +08:00
/ * *
* normalized value is a tuple of the actual normalized options
* and an array of prop keys that need value casting ( booleans and defaults )
* /
2020-09-01 06:32:07 +08:00
export type NormalizedProps = Record < string , NormalizedProp >
2025-02-03 15:46:40 +08:00
2020-09-01 06:32:07 +08:00
export type NormalizedPropsOptions = [ NormalizedProps , string [ ] ] | [ ]
2019-05-28 18:06:00 +08:00
2020-04-07 05:37:47 +08:00
export function initProps (
2019-09-07 00:58:31 +08:00
instance : ComponentInternalInstance ,
2020-04-07 05:37:47 +08:00
rawProps : Data | null ,
isStateful : number , // result of bitwise flag comparison
isSSR = false ,
2024-08-08 23:05:21 +08:00
) : void {
2024-12-10 08:11:36 +08:00
const props : Data = ( instance . props = { } )
2024-04-16 22:47:24 +08:00
const attrs : Data = createInternalObject ( )
2021-03-26 05:26:58 +08:00
instance . propsDefaults = Object . create ( null )
2020-04-07 05:37:47 +08:00
setFullProps ( instance , rawProps , props , attrs )
2021-04-02 08:25:12 +08:00
// ensure all declared prop keys are present
for ( const key in instance . propsOptions [ 0 ] ) {
if ( ! ( key in props ) ) {
props [ key ] = undefined
}
}
2020-04-07 05:37:47 +08:00
// validation
2020-06-09 23:27:40 +08:00
if ( __DEV__ ) {
2024-12-05 16:14:24 +08:00
validateProps ( rawProps || { } , props , instance . propsOptions [ 0 ] ! )
2019-05-28 18:06:00 +08:00
}
2019-05-30 23:16:15 +08:00
2020-04-07 05:37:47 +08:00
if ( isStateful ) {
// stateful
2020-04-15 11:49:46 +08:00
instance . props = isSSR ? props : shallowReactive ( props )
2020-04-07 05:37:47 +08:00
} else {
2020-06-09 23:27:40 +08:00
if ( ! instance . type . props ) {
2020-04-07 05:37:47 +08:00
// functional w/ optional props, props === attrs
instance . props = attrs
} else {
// functional w/ declared props
instance . props = props
}
}
instance . attrs = attrs
}
2019-05-30 23:16:15 +08:00
2025-02-04 22:44:17 +08:00
function isInHmrContext ( instance : GenericComponentInstance | null ) {
2022-08-30 14:42:22 +08:00
while ( instance ) {
if ( instance . type . __hmrId ) return true
instance = instance . parent
}
}
2020-04-07 05:37:47 +08:00
export function updateProps (
instance : ComponentInternalInstance ,
rawProps : Data | null ,
2020-04-21 02:16:25 +08:00
rawPrevProps : Data | null ,
2020-04-07 05:37:47 +08:00
optimized : boolean ,
2024-08-08 23:05:21 +08:00
) : void {
2020-04-07 05:37:47 +08:00
const {
props ,
attrs ,
vnode : { patchFlag } ,
} = instance
const rawCurrentProps = toRaw ( props )
2020-09-01 06:32:07 +08:00
const [ options ] = instance . propsOptions
2021-04-22 07:05:20 +08:00
let hasAttrsChanged = false
2020-04-07 05:37:47 +08:00
2020-08-25 06:13:17 +08:00
if (
2020-10-15 23:05:12 +08:00
// always force full diff in dev
// - #1942 if hmr is enabled with sfc component
// - vite#872 non-sfc component used by sfc component
2022-08-30 14:42:22 +08:00
! ( __DEV__ && isInHmrContext ( instance ) ) &&
2020-08-25 06:13:17 +08:00
( optimized || patchFlag > 0 ) &&
! ( patchFlag & PatchFlags . FULL_PROPS )
) {
2020-04-07 05:37:47 +08:00
if ( patchFlag & PatchFlags . PROPS ) {
// Compiler-generated props & no keys change, just set the updated
// the props.
const propsToUpdate = instance . vnode . dynamicProps !
for ( let i = 0 ; i < propsToUpdate . length ; i ++ ) {
2021-05-01 03:50:32 +08:00
let key = propsToUpdate [ i ]
2022-04-12 14:42:00 +08:00
// skip if the prop key is a declared emit event listener
2022-05-13 07:10:00 +08:00
if ( isEmitListener ( instance . emitsOptions , key ) ) {
2022-04-12 14:42:00 +08:00
continue
}
2020-04-07 05:37:47 +08:00
// PROPS flag guarantees rawProps to be non-null
const value = rawProps ! [ key ]
if ( options ) {
// attr / props separation was done on init and will be consistent
// in this code path, so just check if attrs have it.
if ( hasOwn ( attrs , key ) ) {
2021-04-22 07:05:20 +08:00
if ( value !== attrs [ key ] ) {
attrs [ key ] = value
hasAttrsChanged = true
}
2020-04-07 05:37:47 +08:00
} else {
const camelizedKey = camelize ( key )
props [ camelizedKey ] = resolvePropValue (
options ,
camelizedKey ,
2020-09-18 03:59:01 +08:00
value ,
2021-05-25 07:09:18 +08:00
instance ,
2024-12-03 16:48:28 +08:00
baseResolveDefault ,
2020-04-07 05:37:47 +08:00
)
}
} else {
2021-05-01 03:50:32 +08:00
if ( __COMPAT__ ) {
if ( isOn ( key ) && key . endsWith ( 'Native' ) ) {
key = key . slice ( 0 , - 6 ) // remove Native postfix
} else if ( shouldSkipAttr ( key , instance ) ) {
continue
}
2021-04-20 21:25:12 +08:00
}
2021-04-22 07:05:20 +08:00
if ( value !== attrs [ key ] ) {
attrs [ key ] = value
hasAttrsChanged = true
}
2020-04-07 05:37:47 +08:00
}
}
}
} else {
// full props update.
2021-04-22 07:05:20 +08:00
if ( setFullProps ( instance , rawProps , props , attrs ) ) {
hasAttrsChanged = true
}
2020-04-07 05:37:47 +08:00
// in case of dynamic props, check if we need to delete keys from
// the props object
2020-04-14 00:37:31 +08:00
let kebabKey : string
2020-04-07 05:37:47 +08:00
for ( const key in rawCurrentProps ) {
2020-04-14 00:37:31 +08:00
if (
! rawProps ||
2020-07-07 04:58:46 +08:00
// for camelCase
( ! hasOwn ( rawProps , key ) &&
2020-04-14 00:37:31 +08:00
// it's possible the original props was passed in as kebab-case
// and converted to camelCase (#955)
( ( kebabKey = hyphenate ( key ) ) === key || ! hasOwn ( rawProps , kebabKey ) ) )
) {
2020-04-15 04:17:35 +08:00
if ( options ) {
2020-07-07 04:58:46 +08:00
if (
rawPrevProps &&
2020-06-26 21:16:06 +08:00
// for camelCase
2020-07-07 04:58:46 +08:00
( rawPrevProps [ key ] !== undefined ||
// for kebab-case
rawPrevProps [ kebabKey ! ] !== undefined )
) {
2020-04-21 02:16:25 +08:00
props [ key ] = resolvePropValue (
options ,
key ,
2020-09-18 03:59:01 +08:00
undefined ,
2021-05-25 07:09:18 +08:00
instance ,
2024-12-03 16:48:28 +08:00
baseResolveDefault ,
2021-05-25 07:09:18 +08:00
true /* isAbsent */ ,
2020-04-21 02:16:25 +08:00
)
}
2020-04-15 04:17:35 +08:00
} else {
delete props [ key ]
}
2020-04-07 05:37:47 +08:00
}
}
2020-04-21 02:16:25 +08:00
// in the case of functional component w/o props declaration, props and
// attrs point to the same object so it should already have been updated.
if ( attrs !== rawCurrentProps ) {
for ( const key in attrs ) {
2022-01-21 14:21:42 +08:00
if (
! rawProps ||
( ! hasOwn ( rawProps , key ) &&
( ! __COMPAT__ || ! hasOwn ( rawProps , key + 'Native' ) ) )
) {
2020-04-21 02:16:25 +08:00
delete attrs [ key ]
2021-04-22 07:05:20 +08:00
hasAttrsChanged = true
2020-04-21 02:16:25 +08:00
}
2020-04-07 05:37:47 +08:00
}
}
}
2020-06-13 04:59:15 +08:00
// trigger updates for $attrs in case it's used in component slots
2021-04-22 07:05:20 +08:00
if ( hasAttrsChanged ) {
2024-04-12 16:02:52 +08:00
trigger ( instance . attrs , TriggerOpTypes . SET , '' )
2021-04-22 07:05:20 +08:00
}
2020-06-13 04:59:15 +08:00
2021-03-26 04:22:43 +08:00
if ( __DEV__ ) {
2024-12-05 16:14:24 +08:00
validateProps ( rawProps || { } , props , instance . propsOptions [ 0 ] ! )
2020-04-07 05:37:47 +08:00
}
}
function setFullProps (
instance : ComponentInternalInstance ,
rawProps : Data | null ,
props : Data ,
attrs : Data ,
) {
2020-09-01 06:32:07 +08:00
const [ options , needCastKeys ] = instance . propsOptions
2021-04-22 07:05:20 +08:00
let hasAttrsChanged = false
2021-05-25 06:48:33 +08:00
let rawCastValues : Data | undefined
2020-03-19 06:14:51 +08:00
if ( rawProps ) {
2021-05-01 03:50:32 +08:00
for ( let key in rawProps ) {
2019-11-21 10:56:17 +08:00
// key, ref are reserved and never passed down
2020-02-11 02:15:36 +08:00
if ( isReservedProp ( key ) ) {
continue
}
2021-04-13 06:47:50 +08:00
2021-04-18 11:19:40 +08:00
if ( __COMPAT__ ) {
if ( key . startsWith ( 'onHook:' ) ) {
softAssertCompatEnabled (
DeprecationTypes . INSTANCE_EVENT_HOOKS ,
instance ,
key . slice ( 2 ) . toLowerCase ( ) ,
)
}
if ( key === 'inline-template' ) {
continue
}
2021-04-13 06:47:50 +08:00
}
2021-04-02 08:25:12 +08:00
const value = rawProps [ key ]
2019-10-24 22:59:57 +08:00
// prop option names are camelized during normalization, so to support
// kebab -> camel conversion here we need to camelize the key.
2020-04-04 00:05:52 +08:00
let camelKey
2020-04-07 05:37:47 +08:00
if ( options && hasOwn ( options , ( camelKey = camelize ( key ) ) ) ) {
2021-05-25 06:48:33 +08:00
if ( ! needCastKeys || ! needCastKeys . includes ( camelKey ) ) {
props [ camelKey ] = value
} else {
; ( rawCastValues || ( rawCastValues = { } ) ) [ camelKey ] = value
}
2020-09-01 06:32:07 +08:00
} else if ( ! isEmitListener ( instance . emitsOptions , key ) ) {
2020-04-04 00:05:52 +08:00
// Any non-declared (either as a prop or an emitted event) props are put
// into a separate `attrs` object for spreading. Make sure to preserve
// original key casing
2021-05-01 03:50:32 +08:00
if ( __COMPAT__ ) {
if ( isOn ( key ) && key . endsWith ( 'Native' ) ) {
key = key . slice ( 0 , - 6 ) // remove Native postfix
} else if ( shouldSkipAttr ( key , instance ) ) {
continue
}
2021-04-20 21:25:12 +08:00
}
2021-12-06 13:58:45 +08:00
if ( ! ( key in attrs ) || value !== attrs [ key ] ) {
2021-04-22 07:05:20 +08:00
attrs [ key ] = value
hasAttrsChanged = true
}
2019-05-28 18:06:00 +08:00
}
}
}
2020-04-04 00:05:52 +08:00
2020-04-07 05:37:47 +08:00
if ( needCastKeys ) {
2021-05-25 07:09:18 +08:00
const castValues = rawCastValues || EMPTY_OBJ
2019-12-13 11:07:48 +08:00
for ( let i = 0 ; i < needCastKeys . length ; i ++ ) {
const key = needCastKeys [ i ]
2020-04-21 02:16:25 +08:00
props [ key ] = resolvePropValue (
options ! ,
key ,
2021-05-25 07:09:18 +08:00
castValues [ key ] ,
instance ,
2024-12-03 16:48:28 +08:00
baseResolveDefault ,
2021-05-25 07:09:18 +08:00
! hasOwn ( castValues , key ) ,
2020-04-21 02:16:25 +08:00
)
2019-05-28 18:06:00 +08:00
}
}
2021-04-22 07:05:20 +08:00
return hasAttrsChanged
2020-04-07 05:37:47 +08:00
}
2019-05-29 11:36:16 +08:00
2024-12-03 16:48:28 +08:00
/ * *
* @internal for runtime - vapor
* /
2024-12-03 21:43:18 +08:00
export function resolvePropValue <
T extends GenericComponentInstance & Pick < ComponentInternalInstance , ' ce ' > ,
> (
2020-09-01 06:32:07 +08:00
options : NormalizedProps ,
2020-04-07 05:37:47 +08:00
key : string ,
2020-09-18 03:59:01 +08:00
value : unknown ,
2024-12-03 16:48:28 +08:00
instance : T ,
/ * *
* Allow runtime - specific default resolution logic
* /
resolveDefault : (
factory : ( props : Data ) = > unknown ,
instance : T ,
key : string ,
) = > unknown ,
isAbsent = false ,
) : unknown {
2020-06-25 23:05:39 +08:00
const opt = options [ key ]
2020-04-15 04:17:35 +08:00
if ( opt != null ) {
const hasDefault = hasOwn ( opt , 'default' )
// default values
if ( hasDefault && value === undefined ) {
const defaultValue = opt . default
2023-03-29 22:21:27 +08:00
if (
opt . type !== Function &&
! opt . skipFactory &&
isFunction ( defaultValue )
) {
2024-12-03 16:48:28 +08:00
const cachedDefaults =
instance . propsDefaults || ( instance . propsDefaults = { } )
if ( hasOwn ( cachedDefaults , key ) ) {
value = cachedDefaults [ key ]
2021-03-26 05:26:58 +08:00
} else {
2024-12-03 16:48:28 +08:00
value = cachedDefaults [ key ] = resolveDefault (
defaultValue ,
instance ,
key ,
2021-04-08 03:38:04 +08:00
)
2021-03-26 05:26:58 +08:00
}
2020-09-18 03:59:01 +08:00
} else {
value = defaultValue
}
2024-08-07 02:51:29 +08:00
// #9006 reflect default value on custom element
if ( instance . ce ) {
instance . ce . _setProp ( key , value )
}
2020-04-15 04:17:35 +08:00
}
// boolean casting
if ( opt [ BooleanFlags . shouldCast ] ) {
2021-05-25 07:09:18 +08:00
if ( isAbsent && ! hasDefault ) {
value = false
} else if (
2020-04-15 04:17:35 +08:00
opt [ BooleanFlags . shouldCastTrue ] &&
( value === '' || value === hyphenate ( key ) )
) {
value = true
}
2020-04-07 05:37:47 +08:00
}
2020-03-17 06:45:08 +08:00
}
2020-04-07 05:37:47 +08:00
return value
2020-03-17 06:45:08 +08:00
}
2024-12-03 16:48:28 +08:00
/ * *
* runtime - dom - specific default resolving logic
* /
function baseResolveDefault (
factory : ( props : Data ) = > unknown ,
instance : ComponentInternalInstance ,
key : string ,
) {
let value
const reset = setCurrentInstance ( instance )
const props = toRaw ( instance . props )
value = factory . call (
__COMPAT__ && isCompatEnabled ( DeprecationTypes . PROPS_DEFAULT_THIS , instance )
? createPropsDefaultThis ( instance , props , key )
: null ,
props ,
)
reset ( )
return value
}
2024-07-15 21:56:37 +08:00
const mixinPropsCache = new WeakMap < ConcreteComponent , NormalizedPropsOptions > ( )
2020-03-22 04:25:33 +08:00
export function normalizePropsOptions (
2020-09-01 06:32:07 +08:00
comp : ConcreteComponent ,
appContext : AppContext ,
asMixin = false ,
) : NormalizedPropsOptions {
2024-07-15 21:56:37 +08:00
const cache =
__FEATURE_OPTIONS_API__ && asMixin ? mixinPropsCache : appContext.propsCache
2021-06-03 03:22:52 +08:00
const cached = cache . get ( comp )
if ( cached ) {
return cached
2019-05-28 18:06:00 +08:00
}
2020-06-09 23:27:40 +08:00
const raw = comp . props
2020-04-04 00:05:52 +08:00
const normalized : NormalizedPropsOptions [ 0 ] = { }
2019-12-13 11:07:48 +08:00
const needCastKeys : NormalizedPropsOptions [ 1 ] = [ ]
2020-06-09 23:27:40 +08:00
// apply mixin/extends props
let hasExtends = false
2020-07-21 09:51:30 +08:00
if ( __FEATURE_OPTIONS_API__ && ! isFunction ( comp ) ) {
2020-06-09 23:27:40 +08:00
const extendProps = ( raw : ComponentOptions ) = > {
2021-04-23 02:59:54 +08:00
if ( __COMPAT__ && isFunction ( raw ) ) {
raw = raw . options
}
2020-09-01 06:32:07 +08:00
hasExtends = true
const [ props , keys ] = normalizePropsOptions ( raw , appContext , true )
2020-06-11 04:54:23 +08:00
extend ( normalized , props )
2020-06-09 23:27:40 +08:00
if ( keys ) needCastKeys . push ( . . . keys )
}
2020-09-01 06:32:07 +08:00
if ( ! asMixin && appContext . mixins . length ) {
appContext . mixins . forEach ( extendProps )
}
2020-06-09 23:27:40 +08:00
if ( comp . extends ) {
extendProps ( comp . extends )
}
if ( comp . mixins ) {
comp . mixins . forEach ( extendProps )
}
}
if ( ! raw && ! hasExtends ) {
2022-08-29 11:10:16 +08:00
if ( isObject ( comp ) ) {
cache . set ( comp , EMPTY_ARR as any )
}
2021-06-03 03:22:52 +08:00
return EMPTY_ARR as any
2020-06-09 23:27:40 +08:00
}
2024-12-03 16:48:28 +08:00
baseNormalizePropsOptions ( raw , normalized , needCastKeys )
const res : NormalizedPropsOptions = [ normalized , needCastKeys ]
if ( isObject ( comp ) ) {
cache . set ( comp , res )
}
return res
}
/ * *
* @internal for runtime - vapor only
* /
export function baseNormalizePropsOptions (
raw : ComponentPropsOptions | undefined ,
normalized : NonNullable < NormalizedPropsOptions [ 0 ] > ,
needCastKeys : NonNullable < NormalizedPropsOptions [ 1 ] > ,
) : void {
2019-05-28 18:06:00 +08:00
if ( isArray ( raw ) ) {
for ( let i = 0 ; i < raw . length ; i ++ ) {
if ( __DEV__ && ! isString ( raw [ i ] ) ) {
warn ( ` props must be strings when using array syntax. ` , raw [ i ] )
}
const normalizedKey = camelize ( raw [ i ] )
2020-03-17 06:45:08 +08:00
if ( validatePropName ( normalizedKey ) ) {
2020-04-04 00:05:52 +08:00
normalized [ normalizedKey ] = EMPTY_OBJ
2019-05-28 18:06:00 +08:00
}
}
2020-06-09 23:27:40 +08:00
} else if ( raw ) {
2019-05-28 18:06:00 +08:00
if ( __DEV__ && ! isObject ( raw ) ) {
warn ( ` invalid props options ` , raw )
}
for ( const key in raw ) {
const normalizedKey = camelize ( key )
2020-03-17 06:45:08 +08:00
if ( validatePropName ( normalizedKey ) ) {
2019-05-28 18:06:00 +08:00
const opt = raw [ key ]
2020-04-04 00:05:52 +08:00
const prop : NormalizedProp = ( normalized [ normalizedKey ] =
2023-02-03 09:54:15 +08:00
isArray ( opt ) || isFunction ( opt ) ? { type : opt } : extend ( { } , opt ) )
2024-07-29 21:47:23 +08:00
const propType = prop . type
let shouldCast = false
let shouldCastTrue = true
if ( isArray ( propType ) ) {
for ( let index = 0 ; index < propType . length ; ++ index ) {
const type = propType [ index ]
const typeName = isFunction ( type ) && type . name
if ( typeName === 'Boolean' ) {
shouldCast = true
break
} else if ( typeName === 'String' ) {
// If we find `String` before `Boolean`, e.g. `[String, Boolean]`,
// we need to handle the casting slightly differently. Props
// passed as `<Comp checked="">` or `<Comp checked="checked">`
// will either be treated as strings or converted to a boolean
// `true`, depending on the order of the types.
shouldCastTrue = false
}
2019-12-13 11:07:48 +08:00
}
2024-07-29 21:47:23 +08:00
} else {
shouldCast = isFunction ( propType ) && propType . name === 'Boolean'
}
prop [ BooleanFlags . shouldCast ] = shouldCast
prop [ BooleanFlags . shouldCastTrue ] = shouldCastTrue
// if the prop needs boolean casting or default value
if ( shouldCast || hasOwn ( prop , 'default' ) ) {
needCastKeys . push ( normalizedKey )
2019-05-28 18:06:00 +08:00
}
}
}
}
2020-10-07 03:31:29 +08:00
}
function validatePropName ( key : string ) {
2024-02-09 08:47:06 +08:00
if ( key [ 0 ] !== '$' && ! isReservedProp ( key ) ) {
2020-10-07 03:31:29 +08:00
return true
} else if ( __DEV__ ) {
warn ( ` Invalid prop name: " ${ key } " is a reserved property. ` )
}
return false
2020-04-04 00:05:52 +08:00
}
2024-07-29 21:47:23 +08:00
// dev only
2019-05-28 18:06:00 +08:00
// use function string name to check type constructors
// so that it works across vms / iframes.
2024-04-27 11:48:37 +08:00
function getType ( ctor : Prop < any > | null ) : string {
2024-02-13 11:54:59 +08:00
// Early return for null to avoid unnecessary computations
if ( ctor === null ) {
return 'null'
}
// Avoid using regex for common cases by checking the type directly
if ( typeof ctor === 'function' ) {
// Using name property to avoid converting function to string
return ctor . name || ''
} else if ( typeof ctor === 'object' ) {
// Attempting to directly access constructor name if possible
const name = ctor . constructor && ctor . constructor . name
return name || ''
}
// Fallback for other types (though they're less likely to have meaningful names here)
return ''
2019-05-28 18:06:00 +08:00
}
2020-06-09 23:27:40 +08:00
/ * *
* dev only
2024-12-05 16:14:24 +08:00
* @internal
2020-06-09 23:27:40 +08:00
* /
2024-12-05 16:14:24 +08:00
export function validateProps (
2021-03-26 04:22:43 +08:00
rawProps : Data ,
2024-12-05 16:14:24 +08:00
resolvedProps : Data ,
options : NormalizedProps ,
) : void {
resolvedProps = toRaw ( resolvedProps )
2024-10-11 11:02:58 +08:00
const camelizePropsKey = Object . keys ( rawProps ) . map ( key = > camelize ( key ) )
2020-04-07 05:37:47 +08:00
for ( const key in options ) {
2024-12-05 17:50:09 +08:00
const opt = options [ key ]
if ( opt != null ) {
validateProp (
key ,
resolvedProps [ key ] ,
opt ,
resolvedProps ,
! camelizePropsKey . includes ( key ) ,
)
}
2020-04-07 05:37:47 +08:00
}
}
2020-06-09 23:27:40 +08:00
/ * *
* dev only
* /
2019-05-28 18:06:00 +08:00
function validateProp (
2024-12-05 16:14:24 +08:00
key : string ,
2019-10-22 23:26:48 +08:00
value : unknown ,
2024-12-05 16:14:24 +08:00
propOptions : PropOptions ,
resolvedProps : Data ,
2019-05-28 18:06:00 +08:00
isAbsent : boolean ,
) {
2024-12-05 16:14:24 +08:00
const { type , required , validator , skipCheck } = propOptions
2019-05-28 18:06:00 +08:00
// required!
if ( required && isAbsent ) {
2024-12-05 16:14:24 +08:00
warn ( 'Missing required prop: "' + key + '"' )
2019-05-28 18:06:00 +08:00
return
}
// missing but optional
2023-04-08 12:13:05 +08:00
if ( value == null && ! required ) {
2019-05-28 18:06:00 +08:00
return
}
// type check
2023-03-28 22:29:54 +08:00
if ( type != null && type !== true && ! skipCheck ) {
2019-05-28 18:06:00 +08:00
let isValid = false
const types = isArray ( type ) ? type : [ type ]
const expectedTypes = [ ]
// value is valid as long as one of the specified types match
for ( let i = 0 ; i < types . length && ! isValid ; i ++ ) {
const { valid , expectedType } = assertType ( value , types [ i ] )
expectedTypes . push ( expectedType || '' )
isValid = valid
}
if ( ! isValid ) {
2024-12-05 16:14:24 +08:00
warn ( getInvalidTypeMessage ( key , value , expectedTypes ) )
2019-05-28 18:06:00 +08:00
return
}
}
// custom validator
2024-12-05 17:50:09 +08:00
if (
validator &&
! validator ( value , __DEV__ ? shallowReadonly ( resolvedProps ) : resolvedProps )
) {
2024-12-05 16:14:24 +08:00
warn ( 'Invalid prop: custom validator check failed for prop "' + key + '".' )
2019-05-28 18:06:00 +08:00
}
}
2024-09-04 20:24:33 +08:00
const isSimpleType = /*@__PURE__*/ makeMap (
2021-02-04 02:25:45 +08:00
'String,Number,Boolean,Function,Symbol,BigInt' ,
2019-10-23 22:34:58 +08:00
)
2019-05-28 18:06:00 +08:00
2020-04-07 05:37:47 +08:00
type AssertionResult = {
valid : boolean
expectedType : string
}
2020-06-09 23:27:40 +08:00
/ * *
* dev only
* /
2024-04-27 11:48:37 +08:00
function assertType (
value : unknown ,
type : PropConstructor | null ,
) : AssertionResult {
2019-05-28 18:06:00 +08:00
let valid
const expectedType = getType ( type )
2024-04-27 11:48:37 +08:00
if ( expectedType === 'null' ) {
valid = value === null
} else if ( isSimpleType ( expectedType ) ) {
2019-05-28 18:06:00 +08:00
const t = typeof value
valid = t === expectedType . toLowerCase ( )
// for primitive wrapper objects
if ( ! valid && t === 'object' ) {
2024-04-27 11:48:37 +08:00
valid = value instanceof ( type as PropConstructor )
2019-05-28 18:06:00 +08:00
}
} else if ( expectedType === 'Object' ) {
2020-08-17 23:25:27 +08:00
valid = isObject ( value )
2019-05-28 18:06:00 +08:00
} else if ( expectedType === 'Array' ) {
valid = isArray ( value )
} else {
2024-04-27 11:48:37 +08:00
valid = value instanceof ( type as PropConstructor )
2019-05-28 18:06:00 +08:00
}
return {
valid ,
expectedType ,
}
}
2020-06-09 23:27:40 +08:00
/ * *
* dev only
* /
2019-05-28 18:06:00 +08:00
function getInvalidTypeMessage (
name : string ,
2019-10-22 23:26:48 +08:00
value : unknown ,
2019-05-28 18:06:00 +08:00
expectedTypes : string [ ] ,
) : string {
2023-11-10 14:32:10 +08:00
if ( expectedTypes . length === 0 ) {
return (
` Prop type [] for prop " ${ name } " won't match anything. ` +
` Did you mean to use type Array instead? `
)
}
2019-05-28 18:06:00 +08:00
let message =
` Invalid prop: type check failed for prop " ${ name } ". ` +
2021-07-19 23:20:28 +08:00
` Expected ${ expectedTypes . map ( capitalize ) . join ( ' | ' ) } `
2019-05-28 18:06:00 +08:00
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
}
2020-06-09 23:27:40 +08:00
/ * *
* dev only
* /
2019-10-22 23:26:48 +08:00
function styleValue ( value : unknown , type : string ) : string {
2019-05-28 18:06:00 +08:00
if ( type === 'String' ) {
return ` " ${ value } " `
} else if ( type === 'Number' ) {
return ` ${ Number ( value ) } `
} else {
return ` ${ value } `
}
}
2020-06-09 23:27:40 +08:00
/ * *
* dev only
* /
2019-05-28 18:06:00 +08:00
function isExplicable ( type : string ) : boolean {
const explicitTypes = [ 'string' , 'number' , 'boolean' ]
return explicitTypes . some ( elem = > type . toLowerCase ( ) === elem )
}
2020-06-09 23:27:40 +08:00
/ * *
* dev only
* /
2019-05-28 18:06:00 +08:00
function isBoolean ( . . . args : string [ ] ) : boolean {
return args . some ( elem = > elem . toLowerCase ( ) === 'boolean' )
}