各位靓仔靓女们,大家好! 欢迎来到今天的Vue 3源码解密小课堂。今天咱们就来聊聊Vue 3组件里那些“磨人的小妖精”——props
,看看Vue是如何给它们验明正身,又如何给它们安排默认值的。准备好了吗?Let’s dive in!
一、Props:组件的“身份证”和“户口本”
在Vue的世界里,props
就像组件的身份证和户口本,它定义了组件可以接收哪些数据,这些数据是什么类型,以及如果调用组件的人没给这些数据,组件该怎么办。
// 一个简单的例子
<template>
<div>
<h1>{{ title }}</h1>
<p>作者: {{ author }}</p>
</div>
</template>
<script>
import { defineComponent } from 'vue';
export default defineComponent({
props: {
title: {
type: String,
required: true
},
author: {
type: String,
default: '匿名'
}
},
setup(props) {
console.log(props.title); // 可以访问 title
console.log(props.author); // 可以访问 author
return {};
}
});
</script>
在这个例子中,title
是组件的“身份证”,必须提供,否则Vue会报错;author
是组件的“户口本”,就算调用组件的人没给,Vue也会给它一个默认值“匿名”。
二、Vue 3 源码中的 Props 处理流程
Vue 3在组件初始化时,会对props
进行一系列处理,主要包括以下几个步骤:
- 规范化 Props 定义: 将各种奇奇怪怪的
props
定义方式统一成标准格式。 - 创建 Props 代理: 将
props
代理到组件实例上,方便我们在setup
函数中访问。 - 验证 Props: 检查传入的
props
是否符合定义,类型是否正确,是否缺少必填的props
。 - 设置默认值: 如果
props
有默认值,并且调用者没有提供,就使用默认值。
接下来,我们深入源码,看看这些步骤是如何实现的。
三、Props 规范化 (Normalization)
在Vue 3中,props
的定义方式有很多种,比如:
- 字符串数组:
props: ['title', 'author']
- 对象形式:
props: { title: String, author: { type: String, default: '匿名' } }
为了方便后续处理,Vue需要将这些定义方式统一成标准的对象形式。这个过程主要由normalizeProps
函数完成 (源码位置:packages/runtime-core/src/componentProps.ts
)。
// 简化版
function normalizeProps(raw: ComponentOptions, app: AppContext, isRoot: boolean): NormalizedPropsOptions {
const normalized: NormalizedPropsOptions = {};
if (!raw) {
return normalized;
}
const rawProps = raw.props;
if (!rawProps) {
return normalized;
}
if (isArray(rawProps)) {
for (let i = 0; i < rawProps.length; i++) {
const name = rawProps[i];
if (isString(name)) {
normalized[name] = {}; // 转换为对象形式
} else {
// ... 处理错误情况
}
}
} else if (isObject(rawProps)) {
for (const key in rawProps) {
const rawProp = rawProps[key];
const normalizedProp: NormalizedProp = (normalized[key] =
isObject(rawProp) || isArray(rawProp) ? rawProp : { type: rawProp }); // 转换为对象形式
}
} else {
// ... 处理错误情况
}
return normalized;
}
这个函数接收组件的props
定义 (raw),并返回一个标准化的props
对象 (normalized)。
- 如果是字符串数组,就将每个字符串作为key,创建一个空对象作为value。
- 如果是对象,就直接使用该对象,但如果value不是对象或数组,就将其包装成
{ type: value }
的形式。
四、创建 Props 代理 (Proxy)
为了方便在setup
函数中访问props
,Vue会将props
代理到组件实例上。这个过程主要由useProps
函数完成 (源码位置:packages/runtime-core/src/componentProps.ts
)。
// 简化版
function useProps(
instance: ComponentInternalInstance,
rawProps: Data | null,
isStateful: boolean,
isSSR = false
) {
const resolvedProps = shallowReactive({}); // 创建响应式对象,存储解析后的 props
const props = instance.props = isStateful
? resolvedProps as RawProps // 有状态组件,props 是响应式的
: resolvedProps as Readonly<RawProps>; // 函数式组件,props 是只读的
// ... 处理 attrs 和 listeners
return [props, hasPropsChanged];
function hasPropsChanged(nextProps: Data, prevProps: Data | null): boolean {
// ... 比较 props 是否发生变化
return false; // 简化,始终返回 false
}
}
这个函数做了几件事:
- 创建一个响应式对象
resolvedProps
,用于存储解析后的props
。 - 将
resolvedProps
赋值给组件实例的props
属性。 - 返回
props
对象,以便在setup
函数中使用。
五、Props 验证 (Validation)
Props验证是Vue确保组件接收到正确类型数据的关键环节。如果传入的props
不符合定义,Vue会发出警告,帮助开发者及时发现问题。Props验证的核心逻辑在validateProp
函数中 (源码位置:packages/runtime-core/src/componentProps.ts
)。
// 简化版
const enum Type {
String,
Number,
Boolean,
Array,
Object,
Date,
Function,
Symbol
}
const isFunction = (val: any): val is Function => typeof val === 'function'
const isArray = Array.isArray
const isObject = (val: any): val is Record<any, any> =>
val !== null && typeof val === 'object'
function validateProp(
key: string,
value: any,
prop: PropOptions,
instance: ComponentInternalInstance,
isAbsent: boolean
) {
const { type, required, validator } = prop
// required!
if (required && isAbsent) {
warn(`Missing required prop: "${key}"`, instance)
return
}
if (value == null && !prop.required) {
return
}
const { valid, expectedType } = isValidType(value, type)
if (!valid) {
warn(
getInvalidTypeMessage(key, value, expectedType),
instance
)
return
}
if (validator) {
if (!validator(value)) {
warn(
`Invalid prop: custom validator check failed for prop "${key}".`,
instance
)
}
}
}
function isValidType(value: any, type: PropType<any>): { valid: boolean; expectedType: string[] } {
if (type === null) {
return { valid: true, expectedType: ['any'] }
}
if (!isArray(type)) {
type = [type]
}
const expectedTypes: string[] = []
const valid = type.some(t => {
if (t === String) {
expectedTypes.push('String')
return isString(value)
} else if (t === Number) {
expectedTypes.push('Number')
return isNumber(value)
} else if (t === Boolean) {
expectedTypes.push('Boolean')
return isBoolean(value)
} else if (t === Array) {
expectedTypes.push('Array')
return isArray(value)
} else if (t === Object) {
expectedTypes.push('Object')
return isObject(value)
} else if (t === Date) {
expectedTypes.push('Date')
return value instanceof Date
} else if (t === Function) {
expectedTypes.push('Function')
return isFunction(value)
} else if (t === Symbol) {
expectedTypes.push('Symbol')
return typeof value === 'symbol'
} else {
if (value) {
if (typeof t === 'function') {
expectedTypes.push(t.name || 'Custom Type')
return value instanceof t
} else if (typeof t === 'object' && t !== null && typeof t.then === 'function') {
// Promise
expectedTypes.push('Promise')
return typeof value.then === 'function'
} else {
expectedTypes.push(String(t))
return false
}
}
return false
}
})
return { valid, expectedType: expectedTypes }
}
function isString(val: any): val is string {
return typeof val === 'string'
}
function isNumber(val: any): val is number {
return typeof val === 'number'
}
function isBoolean(val: any): val is boolean {
return typeof val === 'boolean'
}
function getInvalidTypeMessage(
key: string,
value: any,
expectedTypes: string[]
): string {
let message =
`Invalid prop: type check failed for prop "${key}".` +
` Expected ${expectedTypes.join(' | ')}, got ${toRawType(value)} `
return message
}
function toRawType(value: any): string {
return Object.prototype.toString.call(value).slice(8, -1)
}
这个函数接收以下参数:
key
: prop 的名称。value
: 传入的 prop 值。prop
: prop 的定义对象。instance
: 组件实例。isAbsent
: 是否缺少该 prop。
验证逻辑如下:
- 检查是否缺少必填的
props
: 如果required
为true
,并且isAbsent
为true
,则发出警告。 - 检查类型: 如果定义了
type
,则检查传入的value
是否是指定的类型。 如果类型不匹配,则发出警告。 - 检查自定义验证器: 如果定义了
validator
,则调用该函数进行验证。 如果验证失败,则发出警告。
isValidType
函数用于检查传入的value
是否是指定的类型。 它支持以下类型:
String
Number
Boolean
Array
Object
Date
Function
Symbol
以及自定义构造函数 (例如,new MyClass()
)。
六、设置默认值 (Default Values)
如果props
定义了默认值,并且调用者没有提供该props
,Vue会使用默认值。这个过程在resolvePropValue
函数中完成 (源码位置:packages/runtime-core/src/componentProps.ts
)。
// 简化版
function resolvePropValue(
options: NormalizedPropsOptions,
props: Data,
key: string,
value: any,
instance: ComponentInternalInstance,
isAbsent: boolean
) {
const opt = options[key]
if (opt != null) {
const hasDefault = hasOwn(opt, 'default')
// default values
if (hasDefault && value === undefined) {
const defaultValue = opt.default
value =
typeof defaultValue === 'function' && getType(opt.type) !== 'Function'
? defaultValue.call(instance)
: defaultValue
}
}
return value
}
const hasOwn = (val: object, key: string | symbol): key is keyof typeof val =>
Object.prototype.hasOwnProperty.call(val, key)
function getType(fn: PropType<any> | null): Function {
const type = fn && (Array.isArray(fn) ? fn[0] : fn)
return type
}
这个函数接收以下参数:
options
: 标准化的props
定义对象。props
: 传入的props
对象。key
: prop 的名称。value
: 传入的 prop 值。instance
: 组件实例。isAbsent
: 是否缺少该 prop。
如果满足以下条件,则使用默认值:
props
定义中存在default
属性。- 传入的
value
是undefined
(即,调用者没有提供该props
)。
如果default
是一个函数,并且type
不是Function
,则调用该函数,并将组件实例作为this
上下文传递给该函数。 这是为了支持动态默认值。
七、类型检查逻辑 (Type Checking Logic)
Vue 3 的类型检查逻辑主要集中在 isValidType
函数中,它使用 typeof
和 instanceof
来判断传入的值是否符合定义的类型。
Prop Type | Type Check |
---|---|
String |
typeof value === 'string' |
Number |
typeof value === 'number' |
Boolean |
typeof value === 'boolean' |
Array |
Array.isArray(value) |
Object |
typeof value === 'object' && value !== null |
Date |
value instanceof Date |
Function |
typeof value === 'function' |
Symbol |
typeof value === 'symbol' |
Class | value instanceof Class |
八、总结
Vue 3 对props
的处理流程可以总结为以下几点:
- 规范化: 将各种
props
定义方式统一成标准的对象形式。 - 代理: 将
props
代理到组件实例上,方便访问。 - 验证: 检查类型,检查是否缺少必填的
props
,执行自定义验证器。 - 默认值: 如果调用者没有提供
props
,并且定义了默认值,则使用默认值。
通过这些步骤,Vue 3 确保了组件接收到正确类型的数据,并且提供了灵活的默认值设置机制,从而提高了代码的可靠性和可维护性。
好了,今天的Vue 3 props
源码解密小课堂就到这里。希望大家有所收获,下次再见! 各位靓仔靓女们,记得点赞关注哦! 溜了溜了~