为学习Vue基础知识,我动手操作通关了Vue演练场,该演练场教程的目标是快速体验使用 Vue 是什么感受,设置偏好时我选的是选项式 + 单文件组件。以下是我结合深入指南写的总结笔记,希望对Vue初学者有所帮助。
文章目录
- 十三. Props传参
- Props 声明
- 传递 prop 的细节
- Prop 名字格式
- 静态 vs. 动态 Props
- 传递不同的值类型
- 单向数据流
- 更改对象 / 数组类型的 props
- Prop 校验
- 运行时类型检查
- 可为 null 的类型
- Boolean 类型转换
- 十四. Emits事件
- 触发与监听事件
- 事件参数
- 声明触发的事件
- 事件校验
十三. Props传参
Props 声明
子组件可以通过 props 从父组件接受动态数据,同时它需要显式声明它所接受的 props,这样 Vue 才能知道外部传入的哪些是 props,哪些是透传 attribute。
“透传 attribute”指的是传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on 事件监听器。最常见的例子就是 class、style 和 id。“透传 attribute”会被自动原封不动透传到收到它的组件的根元素上。
// 在子组件中
export default {
props: {
msg: String
}
created() {
console.log(this.msg);
}
}
// 或
export default {
props: ['msg']
}
对于以对象形式声明的每个属性,key 是 prop 的名称,而值则是该 prop 预期类型的构造函数。比如,如果要求一个 prop 的值是 number 类型,则可使用 Number 构造函数作为其声明的值。
props声明的作用不只是声明props类型,一定程度上作为组件的文档,它还可以:
- 其他开发者在使用你的组件时传递了错误的类型,在浏览器控制台中抛出警告。
- 显式或隐式地设置默认值
- Boolean 类型的 props 有特别的类型转换规则
传递 prop 的细节
Prop 名字格式
推荐的命名形式:
props 声明用camelCase 形式;
props 传递用kebab-case 形式;
组件名用PascalCase 形式。
<script>javascript">
export default {
props: {
greetingMessage: String
}
}
</script>
<template>
<MyComponent greeting-message="hello" />
</template>
静态 vs. 动态 Props
父组件可以通过 props 向子组件传递固定值,或使用 v-bind 语法传递动态值。
// 在父组件中
<ChildComp msg="Hello World" :type="myObj.type" />
传递不同的值类型
<ChildComp
:likes="42"
:comment-ids="[234, 266, 273]"
:author="{
name: 'Veronica',
company: 'Veridian Dynamics'
}"
v-model="obj"
is-flag
/>
v-model="obj"
等同于逐个绑定obj中的所有属性。
若已声明is-flag
类型为Boolean,则只传is-flag
不传值等同于:is-flag="true"
,不传is-flag
等同于:is-flag="false"
。
单向数据流
所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递,也不允许修改props。
一般来说,我们修改props的需求可以根据需求场景不同用以下两种方法代替:
- 场景1: prop 被用于传入初始值;而子组件想在之后将其作为一个局部数据属性。
export default {
props: ['initialCounter'],
data() {
return {
// this.initialCounter仅用于为counter赋初始值,与counter后续更新无关
counter: this.initialCounter
}
}
}
- 场景2:需要对传入的 prop 值做进一步的转换。
export default {
props: ['size'],
computed: {
// 该 prop 变更时计算属性也会自动更新
normalizedSize() {
return this.size.trim().toLowerCase()
}
}
}
更改对象 / 数组类型的 props
虽然在子组件里修改父组件传下来的对象 / 数组类型的 props内部的值,但在大多数情况下是一种不好的实践,最好不要这样。
代替的方法是子组件抛出一个事件来通知父组件做出改变。
Prop 校验
你可以向 props 选项提供一个带有 props 校验选项的对象,更细致地声明对传入的 props 的校验要求:
export default {
props: {
// 基础类型检查
//(给出 `null` 和 `undefined` 值则会跳过任何类型检查)
propA: Number,
// 多种可能的类型
propB: [String, Number],
// 必传,且为 String 类型
propC: {
type: String,
required: true
},
// 必传但可为 null 的字符串
propD: {
type: [String, null],
required: true
},
// Number 类型的默认值
propE: {
type: Number,
default: 100
},
// 对象类型的默认值
propF: {
type: Object,
// 对象或者数组应当用工厂函数返回。
// 工厂函数会收到组件所接收的原始 props(即父组件传入propF的值)
// 作为参数
default(rawProps) {
return { message: 'hello' }
}
},
// 自定义类型校验函数
// 在 3.4+ 中完整的 props 作为第二个参数传入
propG: {
validator(value, props) {
// The value must match one of these strings
return ['success', 'warning', 'danger'].includes(value)
}
},
// 函数类型的默认值
propH: {
type: Function,
// 不像对象或数组的默认,这不是一个
// 工厂函数。这会是一个用来作为默认值的函数
default() {
return 'Default function'
}
}
}
}
一些补充细节:
- 所有 prop 默认都是可选的,除非声明了 required: true。
- 除 Boolean 外的未传递的可选 prop 将会有一个默认值 undefined。
- Boolean 类型的未传递 prop 将被转换为 false。这可以通过为它设置 default 来更改——例如:设置为 default: undefined 将与非布尔类型的 prop 的行为保持一致。
- 如果声明了 default 值,那么在 prop 的值被解析为 undefined 时,无论 prop 是未被传递还是显式指明的 undefined,都会改为 default 值。
注意 prop 的校验是在组件实例被创建之前,所以实例的属性 (比如 data、computed 等) 将在 default 或 validator 函数中不可用。
运行时类型检查
校验选项中的 type 可以是下列这些原生构造函数:
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol
- Error
也可以自定义类或构造函数,Vue 将会通过 instanceof 来检查类型是否匹配:
class Student {
firstName: string;
lastName: string;
}
class Person {
constructor(firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
}
可为 null 的类型
注意如果type仅为null而非使用数组语法,它将允许任何类型。
Boolean 类型转换
为了更贴近原生 boolean attributes 的行为,声明为 Boolean 类型的 props 有特别的类型转换规则。
<!-- 等同于传入 :disabled="true" -->
<MyComponent disabled />
<!-- 等同于传入 :disabled="false" -->
<MyComponent />
当一个 prop 被声明为允许多种类型时,Boolean 的转换规则也将被应用。然而,当同时允许 String 和 Boolean 时,有一种边缘情况——只有当 Boolean 出现在 String 之前时,Boolean 转换规则才适用:
export default {
props: {
disabled1: [Boolean, Number], // <MyCompo disabled />将被解析为 disabled=true
disabled2: [Boolean, String], // disabled=true
disabled3: [Number, Boolean], // disabled=true
disabled4: [String, Boolean], // disabled=""(空字符串)
}
}
十四. Emits事件
触发与监听事件
父组件除了向子组件传递props、attribute,还可以传递事件,子组件收到事件后可以在需要的时候触发,并向方法中传递参数,实现子组件向父组件传值。
// ChildComp.vue
<script>javascript">
export default {
data() {
msg: '321'
},
emits: ['setMsg'], // 注册事件
created() {
this.$emit('setMsg', this.msg); // 调用事件1
}
}
</script>
<template>
<div @click="$emit('setMsg', msg)">向父组件传值<div> <!-- 调用事件2 -->
</template>
<script>javascript">
export default {
data() {
return {msg: '123'};
},
components: {ChildComp},
methods: {
func1(parameter) {...}
}
}
</script>
<template>
<ChildComp @set-msg="newMsg => msg = newMsg" /> <!-- 传入事件1 -->
<ChildComp @set-msg.once="newMsg => msg = newMsg" /> <!-- 传入事件2 -->
<ChildComp @set-msg.once="func1" /> <!-- 传入事件3 -->
{{msg}}
</template>
事件参数
// 第2~n个参数会成为传入setMsg函数的第1~(n-1)个参数
this.$emit('setMsg', 参数1, 参数2, 参数3);
声明触发的事件
export default {
emits: ['inFocus', 'submit']
}
emits还支持对象语法,并能在执行事件前对传入的参数进行校验。
export default {
emits: {
submit(payload: { email: string, password: string }) {
// 通过返回值为 `true` 还是为 `false` 来判断
// 验证是否通过
}
}
}
推荐完整地声明所有要触发的事件,作为文档记录组件的用法,又能让 Vue 更好地将事件和透传 attribute 作出区分:如果一个原生事件的名字 (例如 click) 被定义在 emits 选项中,则监听器只会监听组件触发的 click 事件而不会再响应原生的 click 事件。
事件校验
使用对象语法就能对emit事件进行校验,但不能校验DOM原生事件。
export default {
emits: {
// 没有校验
click: null,
// 校验 submit 事件
submit: ({ email, password }) => {
if (email && password) {
return true
} else {
console.warn('Invalid submit event payload!')
return false
}
}
},
methods: {
submitForm(email, password) {
this.$emit('submit', { email, password })
}
}
}