2020-04-15 03:49:46 +00:00
import { toRaw , shallowReactive } from '@vue/reactivity'
2019-05-28 10:06:00 +00:00
import {
EMPTY_OBJ ,
camelize ,
hyphenate ,
capitalize ,
isString ,
isFunction ,
isArray ,
2019-06-03 05:44:45 +00:00
isObject ,
2019-09-23 19:36:30 +00:00
hasOwn ,
2019-10-25 15:15:04 +00:00
toRawType ,
2019-10-23 14:34:58 +00:00
PatchFlags ,
2020-02-10 18:15:36 +00:00
makeMap ,
2020-03-21 20:25:33 +00:00
isReservedProp ,
2020-04-06 21:57:27 +00:00
EMPTY_ARR ,
2020-06-10 20:54:23 +00:00
def ,
extend
2019-05-28 10:06:00 +00:00
} from '@vue/shared'
import { warn } from './warning'
2020-06-09 15:27:40 +00:00
import {
Data ,
ComponentInternalInstance ,
ComponentOptions ,
Component
} from './component'
2020-04-04 00:40:34 +00:00
import { isEmitListener } from './componentEmits'
2020-04-15 20:45:20 +00:00
import { InternalObjectKey } from './vnode'
2019-05-28 10:06:00 +00:00
2019-10-08 13:26:09 +00:00
export type ComponentPropsOptions < P = Data > =
| ComponentObjectPropsOptions < P >
| string [ ]
export type ComponentObjectPropsOptions < P = Data > = {
2019-05-31 16:47:05 +00:00
[ K in keyof P ] : Prop < P [ K ] > | null
2019-05-28 10:06:00 +00:00
}
2019-09-06 16:58:31 +00:00
export type Prop < T > = PropOptions < T > | PropType < T >
2019-05-28 10:06:00 +00:00
2019-10-30 15:11:23 +00:00
type DefaultFactory < T > = ( ) = > T | null | undefined
2019-05-31 10:07:43 +00:00
interface PropOptions < T = any > {
2019-05-28 10:06:00 +00:00
type ? : PropType < T > | true | null
required? : boolean
2019-10-30 15:11:23 +00:00
default ? : T | DefaultFactory < T > | null | undefined
2019-10-22 15:26:48 +00:00
validator ? ( value : unknown ) : boolean
2019-05-31 10:07:43 +00:00
}
2019-05-31 16:47:05 +00:00
export type PropType < T > = PropConstructor < T > | PropConstructor < T > [ ]
2020-04-03 13:28:13 +00:00
type PropConstructor < T = any > =
| { new ( . . . args : any [ ] ) : T & object }
| { ( ) : T }
| PropMethod < T >
2020-06-11 21:34:21 +00:00
type PropMethod < T , TConstructor = any > = T extends ( . . . args : any ) = > any // if is function with args
? { new ( ) : TConstructor ; ( ) : T ; readonly prototype : TConstructor } // Create Function like constructor
2020-04-03 13:28:13 +00:00
: never
2019-05-31 16:47:05 +00:00
2019-10-05 14:48:54 +00:00
type RequiredKeys < T , MakeDefaultRequired > = {
2019-06-12 07:43:19 +00:00
[ K in keyof T ] : T [ K ] extends
| { required : true }
2019-10-05 14:48:54 +00:00
| ( MakeDefaultRequired extends true ? { default : any } : never )
2019-06-12 07:43:19 +00:00
? K
: never
2019-05-31 16:47:05 +00:00
} [ keyof T ]
2019-10-05 14:48:54 +00:00
type OptionalKeys < T , MakeDefaultRequired > = Exclude <
2019-06-12 07:43:19 +00:00
keyof T ,
2019-10-05 14:48:54 +00:00
RequiredKeys < T , MakeDefaultRequired >
2019-06-12 07:43:19 +00:00
>
2019-05-31 16:47:05 +00:00
type InferPropType < T > = T extends null
2019-06-01 09:43:41 +00:00
? any // null & true would fail to infer
: T extends { type : null | true }
2020-06-09 14:37:00 +00: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`
2019-10-22 15:52:29 +00:00
: T extends ObjectConstructor | { type : ObjectConstructor }
? { [ key : string ] : any }
2020-06-09 14:37:00 +00:00
: T extends BooleanConstructor | { type : BooleanConstructor }
? boolean
: T extends Prop < infer V > ? V : T
2019-05-31 16:47:05 +00:00
2019-06-12 07:43:19 +00:00
export type ExtractPropTypes <
O ,
2019-10-05 14:48:54 +00:00
MakeDefaultRequired extends boolean = true
2019-06-12 07:43:19 +00:00
> = O extends object
2019-11-09 23:40:25 +00:00
? { [ K in RequiredKeys < O , MakeDefaultRequired > ] : InferPropType < O [ K ] > } &
{ [ K in OptionalKeys < O , MakeDefaultRequired > ] ? : InferPropType < O [ K ] > }
2019-05-31 16:47:05 +00:00
: { [ K in string ] : any }
2019-05-28 10:06:00 +00:00
const enum BooleanFlags {
2019-11-24 21:00:46 +00:00
shouldCast ,
shouldCastTrue
2019-05-28 10:06:00 +00:00
}
2019-05-31 16:47:05 +00:00
type NormalizedProp =
| null
| ( PropOptions & {
[ BooleanFlags . shouldCast ] ? : boolean
[ BooleanFlags . shouldCastTrue ] ? : boolean
} )
2019-05-28 10:06:00 +00:00
2019-12-13 03:07:48 +00: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-06-09 15:27:40 +00:00
export type NormalizedPropsOptions = [ Record < string , NormalizedProp > , string [ ] ]
2019-05-28 10:06:00 +00:00
2020-04-06 21:37:47 +00:00
export function initProps (
2019-09-06 16:58:31 +00:00
instance : ComponentInternalInstance ,
2020-04-06 21:37:47 +00:00
rawProps : Data | null ,
isStateful : number , // result of bitwise flag comparison
isSSR = false
2019-05-29 03:36:16 +00:00
) {
2020-04-06 21:37:47 +00:00
const props : Data = { }
const attrs : Data = { }
2020-04-15 20:45:20 +00:00
def ( attrs , InternalObjectKey , 1 )
2020-04-06 21:37:47 +00:00
setFullProps ( instance , rawProps , props , attrs )
// validation
2020-06-09 15:27:40 +00:00
if ( __DEV__ ) {
validateProps ( props , instance . type )
2019-05-28 10:06:00 +00:00
}
2019-05-30 15:16:15 +00:00
2020-04-06 21:37:47 +00:00
if ( isStateful ) {
// stateful
2020-04-15 03:49:46 +00:00
instance . props = isSSR ? props : shallowReactive ( props )
2020-04-06 21:37:47 +00:00
} else {
2020-06-09 15:27:40 +00:00
if ( ! instance . type . props ) {
2020-04-06 21:37:47 +00:00
// functional w/ optional props, props === attrs
instance . props = attrs
} else {
// functional w/ declared props
instance . props = props
}
}
instance . attrs = attrs
}
2019-05-30 15:16:15 +00:00
2020-04-06 21:37:47 +00:00
export function updateProps (
instance : ComponentInternalInstance ,
rawProps : Data | null ,
2020-04-20 18:16:25 +00:00
rawPrevProps : Data | null ,
2020-04-06 21:37:47 +00:00
optimized : boolean
) {
const {
props ,
attrs ,
vnode : { patchFlag }
} = instance
const rawCurrentProps = toRaw ( props )
2020-06-09 15:27:40 +00:00
const [ options ] = normalizePropsOptions ( instance . type )
2020-04-06 21:37:47 +00:00
if ( ( optimized || patchFlag > 0 ) && ! ( patchFlag & PatchFlags . FULL_PROPS ) ) {
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 ++ ) {
const key = propsToUpdate [ i ]
// 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 ) ) {
attrs [ key ] = value
} else {
const camelizedKey = camelize ( key )
props [ camelizedKey ] = resolvePropValue (
options ,
rawCurrentProps ,
camelizedKey ,
value
)
}
} else {
attrs [ key ] = value
}
}
}
} else {
// full props update.
setFullProps ( instance , rawProps , props , attrs )
// in case of dynamic props, check if we need to delete keys from
// the props object
2020-04-13 16:37:31 +00:00
let kebabKey : string
2020-04-06 21:37:47 +00:00
for ( const key in rawCurrentProps ) {
2020-04-13 16:37:31 +00:00
if (
! rawProps ||
( ! hasOwn ( rawProps , key ) &&
// 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-14 20:17:35 +00:00
if ( options ) {
2020-04-20 18:16:25 +00:00
if ( rawPrevProps && rawPrevProps [ kebabKey ! ] !== undefined ) {
props [ key ] = resolvePropValue (
options ,
rawProps || EMPTY_OBJ ,
key ,
undefined
)
}
2020-04-14 20:17:35 +00:00
} else {
delete props [ key ]
}
2020-04-06 21:37:47 +00:00
}
}
2020-04-20 18:16:25 +00: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 ) {
if ( ! rawProps || ! hasOwn ( rawProps , key ) ) {
delete attrs [ key ]
}
2020-04-06 21:37:47 +00:00
}
}
}
2020-06-09 15:27:40 +00:00
if ( __DEV__ && rawProps ) {
validateProps ( props , instance . type )
2020-04-06 21:37:47 +00:00
}
}
function setFullProps (
instance : ComponentInternalInstance ,
rawProps : Data | null ,
props : Data ,
attrs : Data
) {
2020-06-09 15:27:40 +00:00
const [ options , needCastKeys ] = normalizePropsOptions ( instance . type )
2020-04-06 21:37:47 +00:00
const emits = instance . type . emits
2020-03-18 22:14:51 +00:00
if ( rawProps ) {
2019-05-29 01:18:45 +00:00
for ( const key in rawProps ) {
2020-02-10 18:15:36 +00:00
const value = rawProps [ key ]
2019-11-21 02:56:17 +00:00
// key, ref are reserved and never passed down
2020-02-10 18:15:36 +00:00
if ( isReservedProp ( key ) ) {
continue
}
2019-10-24 14:59:57 +00:00
// prop option names are camelized during normalization, so to support
// kebab -> camel conversion here we need to camelize the key.
2020-04-03 16:05:52 +00:00
let camelKey
2020-04-06 21:37:47 +00:00
if ( options && hasOwn ( options , ( camelKey = camelize ( key ) ) ) ) {
props [ camelKey ] = value
2020-04-03 23:08:17 +00:00
} else if ( ! emits || ! isEmitListener ( emits , key ) ) {
2020-04-03 16:05:52 +00: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
2020-04-06 21:37:47 +00:00
attrs [ key ] = value
2019-05-28 10:06:00 +00:00
}
}
}
2020-04-03 16:05:52 +00:00
2020-04-06 21:37:47 +00:00
if ( needCastKeys ) {
2020-04-20 18:16:25 +00:00
const rawCurrentProps = toRaw ( props )
2019-12-13 03:07:48 +00:00
for ( let i = 0 ; i < needCastKeys . length ; i ++ ) {
const key = needCastKeys [ i ]
2020-04-20 18:16:25 +00:00
props [ key ] = resolvePropValue (
options ! ,
rawCurrentProps ,
key ,
rawCurrentProps [ key ]
)
2019-05-28 10:06:00 +00:00
}
}
2020-04-06 21:37:47 +00:00
}
2019-05-29 03:36:16 +00:00
2020-04-06 21:37:47 +00:00
function resolvePropValue (
options : NormalizedPropsOptions [ 0 ] ,
props : Data ,
key : string ,
value : unknown
) {
2020-04-14 20:17:35 +00:00
const opt = options [ key ]
if ( opt != null ) {
const hasDefault = hasOwn ( opt , 'default' )
// default values
if ( hasDefault && value === undefined ) {
const defaultValue = opt . default
value = isFunction ( defaultValue ) ? defaultValue ( ) : defaultValue
}
// boolean casting
if ( opt [ BooleanFlags . shouldCast ] ) {
if ( ! hasOwn ( props , key ) && ! hasDefault ) {
value = false
} else if (
opt [ BooleanFlags . shouldCastTrue ] &&
( value === '' || value === hyphenate ( key ) )
) {
value = true
}
2020-04-06 21:37:47 +00:00
}
2020-03-16 22:45:08 +00:00
}
2020-04-06 21:37:47 +00:00
return value
2020-03-16 22:45:08 +00:00
}
2020-03-21 20:25:33 +00:00
export function normalizePropsOptions (
2020-06-09 15:27:40 +00:00
comp : Component
2020-04-06 21:37:47 +00:00
) : NormalizedPropsOptions | [ ] {
2020-06-09 15:27:40 +00:00
if ( comp . __props ) {
return comp . __props
2019-05-28 10:06:00 +00:00
}
2020-06-09 15:27:40 +00:00
const raw = comp . props
2020-04-03 16:05:52 +00:00
const normalized : NormalizedPropsOptions [ 0 ] = { }
2019-12-13 03:07:48 +00:00
const needCastKeys : NormalizedPropsOptions [ 1 ] = [ ]
2020-06-09 15:27:40 +00:00
// apply mixin/extends props
let hasExtends = false
if ( __FEATURE_OPTIONS__ && ! isFunction ( comp ) ) {
const extendProps = ( raw : ComponentOptions ) = > {
const [ props , keys ] = normalizePropsOptions ( raw )
2020-06-10 20:54:23 +00:00
extend ( normalized , props )
2020-06-09 15:27:40 +00:00
if ( keys ) needCastKeys . push ( . . . keys )
}
if ( comp . extends ) {
hasExtends = true
extendProps ( comp . extends )
}
if ( comp . mixins ) {
hasExtends = true
comp . mixins . forEach ( extendProps )
}
}
if ( ! raw && ! hasExtends ) {
return ( comp . __props = EMPTY_ARR )
}
2019-05-28 10:06:00 +00: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-16 22:45:08 +00:00
if ( validatePropName ( normalizedKey ) ) {
2020-04-03 16:05:52 +00:00
normalized [ normalizedKey ] = EMPTY_OBJ
2019-05-28 10:06:00 +00:00
}
}
2020-06-09 15:27:40 +00:00
} else if ( raw ) {
2019-05-28 10:06:00 +00:00
if ( __DEV__ && ! isObject ( raw ) ) {
warn ( ` invalid props options ` , raw )
}
for ( const key in raw ) {
const normalizedKey = camelize ( key )
2020-03-16 22:45:08 +00:00
if ( validatePropName ( normalizedKey ) ) {
2019-05-28 10:06:00 +00:00
const opt = raw [ key ]
2020-04-03 16:05:52 +00:00
const prop : NormalizedProp = ( normalized [ normalizedKey ] =
2019-05-28 10:06:00 +00:00
isArray ( opt ) || isFunction ( opt ) ? { type : opt } : opt )
2020-03-18 22:14:51 +00:00
if ( prop ) {
2019-05-28 10:06:00 +00:00
const booleanIndex = getTypeIndex ( Boolean , prop . type )
const stringIndex = getTypeIndex ( String , prop . type )
2019-05-31 16:47:05 +00:00
prop [ BooleanFlags . shouldCast ] = booleanIndex > - 1
2020-03-16 14:19:06 +00:00
prop [ BooleanFlags . shouldCastTrue ] =
stringIndex < 0 || booleanIndex < stringIndex
2019-12-13 03:07:48 +00:00
// if the prop needs boolean casting or default value
if ( booleanIndex > - 1 || hasOwn ( prop , 'default' ) ) {
needCastKeys . push ( normalizedKey )
}
2019-05-28 10:06:00 +00:00
}
}
}
}
2020-04-03 16:05:52 +00:00
const normalizedEntry : NormalizedPropsOptions = [ normalized , needCastKeys ]
2020-06-09 15:27:40 +00:00
comp . __props = normalizedEntry
2020-04-03 16:05:52 +00:00
return normalizedEntry
}
2019-05-28 10:06:00 +00:00
// use function string name to check type constructors
// so that it works across vms / iframes.
function getType ( ctor : Prop < any > ) : string {
const match = ctor && ctor . toString ( ) . match ( /^\s*function (\w+)/ )
return match ? match [ 1 ] : ''
}
function isSameType ( a : Prop < any > , b : Prop < any > ) : boolean {
return getType ( a ) === getType ( b )
}
function getTypeIndex (
type : Prop < any > ,
expectedTypes : PropType < any > | void | null | true
) : number {
if ( isArray ( expectedTypes ) ) {
for ( let i = 0 , len = expectedTypes . length ; i < len ; i ++ ) {
if ( isSameType ( expectedTypes [ i ] , type ) ) {
return i
}
}
2020-03-16 14:19:06 +00:00
} else if ( isFunction ( expectedTypes ) ) {
2019-05-28 10:06:00 +00:00
return isSameType ( expectedTypes , type ) ? 0 : - 1
}
return - 1
}
2020-06-09 15:27:40 +00:00
/ * *
* dev only
* /
function validateProps ( props : Data , comp : Component ) {
2020-04-06 21:37:47 +00:00
const rawValues = toRaw ( props )
2020-06-09 15:27:40 +00:00
const options = normalizePropsOptions ( comp ) [ 0 ]
2020-04-06 21:37:47 +00:00
for ( const key in options ) {
let opt = options [ key ]
if ( opt == null ) continue
validateProp ( key , rawValues [ key ] , opt , ! hasOwn ( rawValues , key ) )
}
}
2020-06-09 15:27:40 +00:00
/ * *
* dev only
* /
2020-04-06 21:37:47 +00:00
function validatePropName ( key : string ) {
if ( key [ 0 ] !== '$' ) {
return true
} else if ( __DEV__ ) {
warn ( ` Invalid prop name: " ${ key } " is a reserved property. ` )
}
return false
2019-05-28 10:06:00 +00:00
}
2020-06-09 15:27:40 +00:00
/ * *
* dev only
* /
2019-05-28 10:06:00 +00:00
function validateProp (
name : string ,
2019-10-22 15:26:48 +00:00
value : unknown ,
prop : PropOptions ,
2019-05-28 10:06:00 +00:00
isAbsent : boolean
) {
const { type , required , validator } = prop
// required!
if ( required && isAbsent ) {
warn ( 'Missing required prop: "' + name + '"' )
return
}
// missing but optional
if ( value == null && ! prop . required ) {
return
}
// type check
if ( type != null && type !== true ) {
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 ) {
warn ( getInvalidTypeMessage ( name , value , expectedTypes ) )
return
}
}
// custom validator
if ( validator && ! validator ( value ) ) {
warn ( 'Invalid prop: custom validator check failed for prop "' + name + '".' )
}
}
2019-10-23 14:34:58 +00:00
const isSimpleType = /*#__PURE__*/ makeMap (
'String,Number,Boolean,Function,Symbol'
)
2019-05-28 10:06:00 +00:00
2020-04-06 21:37:47 +00:00
type AssertionResult = {
valid : boolean
expectedType : string
}
2020-06-09 15:27:40 +00:00
/ * *
* dev only
* /
2019-10-22 15:26:48 +00:00
function assertType ( value : unknown , type : PropConstructor ) : AssertionResult {
2019-05-28 10:06:00 +00:00
let valid
const expectedType = getType ( type )
2019-10-23 14:34:58 +00:00
if ( isSimpleType ( expectedType ) ) {
2019-05-28 10:06:00 +00:00
const t = typeof value
valid = t === expectedType . toLowerCase ( )
// for primitive wrapper objects
if ( ! valid && t === 'object' ) {
valid = value instanceof type
}
} else if ( expectedType === 'Object' ) {
valid = toRawType ( value ) === 'Object'
} else if ( expectedType === 'Array' ) {
valid = isArray ( value )
} else {
valid = value instanceof type
}
return {
valid ,
expectedType
}
}
2020-06-09 15:27:40 +00:00
/ * *
* dev only
* /
2019-05-28 10:06:00 +00:00
function getInvalidTypeMessage (
name : string ,
2019-10-22 15:26:48 +00:00
value : unknown ,
2019-05-28 10:06:00 +00:00
expectedTypes : string [ ]
) : string {
let message =
` Invalid prop: type check failed for prop " ${ name } ". ` +
` Expected ${ expectedTypes . map ( capitalize ) . join ( ', ' ) } `
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 15:27:40 +00:00
/ * *
* dev only
* /
2019-10-22 15:26:48 +00:00
function styleValue ( value : unknown , type : string ) : string {
2019-05-28 10:06:00 +00:00
if ( type === 'String' ) {
return ` " ${ value } " `
} else if ( type === 'Number' ) {
return ` ${ Number ( value ) } `
} else {
return ` ${ value } `
}
}
2020-06-09 15:27:40 +00:00
/ * *
* dev only
* /
2019-05-28 10:06:00 +00:00
function isExplicable ( type : string ) : boolean {
const explicitTypes = [ 'string' , 'number' , 'boolean' ]
return explicitTypes . some ( elem = > type . toLowerCase ( ) === elem )
}
2020-06-09 15:27:40 +00:00
/ * *
* dev only
* /
2019-05-28 10:06:00 +00:00
function isBoolean ( . . . args : string [ ] ) : boolean {
return args . some ( elem = > elem . toLowerCase ( ) === 'boolean' )
}