各位观众老爷们,大家好!今天咱们来聊聊 Vue 3 源码里那些关于 props
的秘密。别担心,咱们尽量用大白话,加上代码示例,保证让你听得懂,学得会,还能举一反三。
开场白:Props 的重要性,就像房子的地基
想象一下,props
在 Vue 组件里扮演的角色,就像房子的地基。地基不稳,房子就塌。props
如果没处理好,组件的数据来源就不可靠,组件的行为就难以预测,最后整个应用都会变得像一堆意大利面条一样混乱。所以,props
校验和默认值,那是绝对不能马虎的!
第一部分:Props 的定义方式,明明白白你的家底
在 Vue 3 里面,定义 props
主要有两种方式:
-
数组形式 (简单粗暴型)
// MyComponent.vue export default { props: ['message', 'count'], template: '<div>{{ message }} - {{ count }}</div>' }
这种方式简单是简单,但是信息量不足。Vue 3 会自动将
message
和count
当作字符串类型处理,如果需要更精确的类型和校验,那就得靠第二种方式了。 -
对象形式 (精打细算型)
// MyComponent.vue export default { props: { message: { type: String, required: true, default: 'Hello', validator: (value) => { return value.length > 5; } }, count: { type: Number, default: 0, validator: (value) => { return value >= 0; } }, items: { type: Array, default: () => [] // 注意:数组/对象类型的 default 必须是一个工厂函数 }, user: { type: Object, default: () => ({ name: 'Guest' }) // 注意:数组/对象类型的 default 必须是一个工厂函数 } }, template: '<div>{{ message }} - {{ count }}</div>' }
这种方式才是正儿八经的
props
定义方式。它允许你指定type
(类型)、required
(是否必填)、default
(默认值) 和validator
(自定义校验函数)。类型(Type)有哪些?
Vue 3 提供了以下几种类型:
类型 描述 String
字符串 Number
数字 Boolean
布尔值 Array
数组 Object
对象 Date
Date 对象 Function
函数 Symbol
Symbol 对象 null
允许任何类型 any
允许任何类型,不进行类型检查 Array<String>
字符串数组 [String, Number]
多种类型,如字符串或数字 default 的注意事项
当
props
的类型是Array
或Object
的时候,default
必须是一个工厂函数。 为什么? 因为如果直接赋值一个对象或数组,那么所有使用该组件的实例都会共享同一个对象或数组,这可能会导致一些意想不到的副作用。validator 的妙用
validator
允许你自定义校验逻辑。它接收prop
的值作为参数,必须返回一个Boolean
值。如果返回false
,Vue 会在控制台抛出一个警告。
第二部分:源码剖析,看看 Vue 3 内部是怎么干的
现在,咱们深入 Vue 3 的源码,看看它是如何处理 props
的校验和默认值设置的。
-
resolveProps
函数:Props 解析的入口在组件的渲染过程中,
resolveProps
函数负责解析组件的props
选项。这个函数位于packages/runtime-core/src/componentProps.ts
文件中 (路径可能因 Vue 版本而略有不同)。resolveProps
函数的主要任务:- 规范化
props
选项: 将数组形式的props
转换为对象形式。 - 收集
props
信息: 从props
选项中提取类型、是否必填、默认值和校验器等信息。 - 执行类型检查和校验: 检查传入的
props
值是否符合类型要求,并执行自定义的校验器。 - 设置默认值: 如果
props
没有传入值,则使用默认值。
- 规范化
-
validateProp
函数:类型检查和校验的核心validateProp
函数负责执行具体的类型检查和校验逻辑。 这个函数也位于packages/runtime-core/src/componentProps.ts
文件中。validateProp
函数的主要步骤:-
类型检查: 根据
prop
的type
选项,检查传入的值是否符合类型要求。 Vue 3 使用isString
,isNumber
,isBoolean
,isArray
,isObject
,isDate
,isFunction
,isSymbol
等函数来判断值的类型。function validateProp( key: string, value: any, propOptions: PropOptions, vm?: ComponentInternalInstance, presence?: boolean ) { const { type, required, validator } = propOptions // ... 省略类型检查和校验逻辑 }
例如,如果
prop
的type
是Number
,但传入的值是字符串,Vue 就会在控制台抛出一个警告。 -
必填检查: 如果
prop
的required
选项为true
,但没有传入值,Vue 也会在控制台抛出一个警告。 -
校验器执行: 如果
prop
定义了validator
函数,Vue 会调用该函数,并将prop
的值作为参数传递给它。如果validator
函数返回false
,Vue 会在控制台抛出一个警告。
-
-
hasOwn
函数:判断对象是否包含某个属性在
resolveProps
函数中,Vue 3 使用hasOwn
函数来判断组件实例是否已经存在某个prop
。 这样可以避免重复设置prop
的值。const hasOwn = ( val: object, key: string | symbol ): key is keyof typeof val => Object.prototype.hasOwnProperty.call(val, key)
-
默认值设置:
如果一个
prop
没有传入值,并且定义了default
选项,Vue 3 会使用default
选项的值作为prop
的默认值。 如果default
是一个函数,Vue 3 会调用该函数,并将返回值作为prop
的默认值。 这个过程发生在resolveProps
内部。
第三部分:代码示例,加深理解
为了让你更好地理解 Vue 3 源码是如何处理 props
的校验和默认值设置的,咱们来看几个代码示例。
示例 1:类型检查
// MyComponent.vue
export default {
props: {
age: {
type: Number,
required: true
}
},
template: '<div>{{ age }}</div>'
}
如果在使用 MyComponent
组件时,传入的 age
不是一个数字,Vue 3 会在控制台抛出一个警告。
<!-- 错误的使用方式 -->
<MyComponent age="20" />
示例 2:默认值设置
// MyComponent.vue
export default {
props: {
name: {
type: String,
default: 'Guest'
}
},
template: '<div>Hello, {{ name }}!</div>'
}
如果在使用 MyComponent
组件时,没有传入 name
属性,那么组件会显示 "Hello, Guest!"。
<!-- 正确的使用方式 -->
<MyComponent /> <!-- 显示 "Hello, Guest!" -->
<MyComponent name="Alice" /> <!-- 显示 "Hello, Alice!" -->
示例 3:自定义校验器
// MyComponent.vue
export default {
props: {
email: {
type: String,
validator: (value) => {
// 简单的邮箱格式校验
return /^[^s@]+@[^s@]+.[^s@]+$/.test(value);
}
}
},
template: '<div>{{ email }}</div>'
}
如果在使用 MyComponent
组件时,传入的 email
不是一个有效的邮箱地址,Vue 3 会在控制台抛出一个警告。
第四部分: Props 的最佳实践,让你的组件更健壮
-
明确定义类型: 尽量为每个
prop
指定类型。 这样可以提高代码的可读性和可维护性,并且可以帮助 Vue 3 进行类型检查,及时发现错误。 -
使用
required
选项: 如果一个prop
是必须的,一定要使用required: true
。 这样可以确保组件能够接收到必要的数据,避免出现意想不到的错误。 -
合理设置默认值: 为
prop
设置合理的默认值,可以提高组件的灵活性和可用性。 即使没有传入prop
的值,组件也能正常工作。 -
编写自定义校验器: 如果需要更复杂的校验逻辑,可以使用
validator
函数。 这样可以确保prop
的值符合特定的要求,提高组件的健壮性。 -
使用
defineProps
(在<script setup>
中): 在使用单文件组件和<script setup>
语法时,可以使用defineProps
宏来定义props
。它提供了类型推断,并且更加简洁。<script setup> import { defineProps } from 'vue' const props = defineProps({ title: { type: String, required: true }, likes: { type: Number, default: 0 } }) console.log(props.title) </script> <template> <h1>{{ title }}</h1> <p>Likes: {{ likes }}</p> </template>
第五部分:Props 和 Emits 配合使用
props
用于父组件向子组件传递数据,而 emits
用于子组件向父组件传递事件。 props
和 emits
就像是组件之间沟通的桥梁,只有配合使用,才能构建出复杂的交互逻辑。
示例:
// ChildComponent.vue
<template>
<button @click="handleClick">Click me</button>
</template>
<script setup>
import { defineEmits } from 'vue'
const emit = defineEmits(['update:count'])
const handleClick = () => {
emit('update:count', 1)
}
</script>
// ParentComponent.vue
<template>
<ChildComponent @update:count="handleUpdateCount" />
<p>Count: {{ count }}</p>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
const count = ref(0)
const handleUpdateCount = (value) => {
count.value += value
}
</script>
在这个例子中,ChildComponent
使用 emit
发送一个 update:count
事件,并将 1
作为参数传递给父组件。 ParentComponent
监听 update:count
事件,并在 handleUpdateCount
函数中更新 count
的值。 这种模式通常用于实现双向绑定。
第六部分:类型推断的福音 – TypeScript 和 Props
如果你正在使用 TypeScript,那么 props
的类型检查将会变得更加强大。 Vue 3 提供了对 TypeScript 的良好支持,可以根据 props
的类型定义,自动推断出 prop
的类型。
示例:
// MyComponent.vue
import { defineComponent } from 'vue';
export default defineComponent({
props: {
name: {
type: String,
required: true
},
age: {
type: Number,
default: 0
}
},
setup(props) {
// props.name 的类型是 string,props.age 的类型是 number
console.log(props.name.toUpperCase());
console.log(props.age + 1);
return {};
}
});
在这个例子中,TypeScript 可以根据 props
的类型定义,自动推断出 props.name
的类型是 string
,props.age
的类型是 number
。 这可以帮助你在编译时发现类型错误,提高代码的质量。
总结:Props 是组件的生命线
总而言之,props
是 Vue 组件的生命线。 合理的 props
定义和校验,可以确保组件的数据来源可靠,行为可预测,提高代码的可读性和可维护性。 希望通过今天的讲解,你能够更好地理解 Vue 3 源码是如何处理 props
的,并且能够将这些知识应用到实际开发中。
好了,今天的讲座就到这里。 感谢各位的收看,咱们下次再见!