当前位置:网站首页>08 【Props 组件事件】
08 【Props 组件事件】
2022-08-10 09:03:00 【DSelegent】
15.Props
15.1 Props 声明#
Props 是一种特别的 attributes,你可以在组件上声明注册。要传递给子组件内容,我们必须在组件的 props 列表上声明它。
<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
</script>
<template>
<h4>{
{ title }}</h4>
</template>
defineProps 是一个仅 <script setup> 中可用的编译宏命令,并不需要显式地导入。声明的 props 会自动暴露给模板。defineProps 会返回一个对象,其中包含了可以传递给组件的所有 props:
const props = defineProps(['title'])
console.log(props.title)
TypeScript 用户请参考:为组件 props 标注类型
在没有使用 <script setup> 的组件中,props 必须以 props 选项的方式声明,props 对象会作为 setup() 函数的第一个参数被传入:
export default {
props: ['title'],
setup(props) {
// setup() 接收 props 作为第一个参数
console.log(props.title)
}
}
注意传递给 defineProps() 的参数和提供给 props 选项的值是相同的,两种声明方式背后其实使用的都是 prop 选项。
除了使用字符串数组来声明 prop 外,还可以使用对象的形式:
// 使用 <script setup>
defineProps({
title: String,
likes: Number
})
// 非 <script setup>
export default {
props: {
title: String,
likes: Number
}
}
对于以对象形式声明中的每个属性,key 是 prop 的名称,而值则是该 prop 预期类型的构造函数。比如,如果要求一个 prop 的值是 number 类型,则可使用 Number 构造函数作为其声明的值。
对象形式的 props 声明不仅可以一定程度上作为组件的文档,而且如果其他开发者在使用你的组件时传递了错误的类型,也会在浏览器控制台中抛出警告。我们将在本章节稍后进一步讨论有关 prop 校验的更多细节。
校验选项中的
type可以是下列这些原生构造函数:
StringNumberBooleanArrayObjectDateFunctionSymbol另外,
type也可以是自定义的类或构造函数,Vue 将会通过instanceof来检查类型是否匹配。
如果你正在搭配 TypeScript 使用 <script setup>,也可以使用类型标注来声明 props:
<script setup lang="ts">
defineProps<{
title?: string
likes?: number
}>()
</script>
更多关于基于类型的声明的细节请参考组件 props 类型标注。
15.2 Prop 名字格式#
如果一个 prop 的名字很长,应使用 camelCase 形式,因为它们是合法的 JavaScript 标识符,可以直接在模板的表达式中使用,也可以避免在作为属性 key 名时必须加上引号。
defineProps({
greetingMessage: String
})
<span>{
{
greetingMessage }}</span>
虽然理论上你也可以在向子组件传递 props 时使用 camelCase 形式 (使用 DOM 模板时例外),但实际上为了和 HTML attribute 对齐,我们通常会将其写为 kebab-case 形式:
<MyComponent greeting-message="hello" />
对于组件名我们推荐使用 PascalCase,因为这提高了模板的可读性,能帮助我们区分 Vue 组件和原生 HTML 元素。然而对于传递 props 来说,使用 camelCase 并没有太多优势,因此我们推荐更贴近 HTML 的书写风格。
15.3 使用一个对象绑定多个 prop#
如果你想要将一个对象的所有属性都当作 props 传入,你可以使用没有参数的 v-bind,即只使用 v-bind 而非 :prop-name。例如,这里有一个 post 对象:
const post = {
id: 1,
title: 'My Journey with Vue'
}
以及下面的模板:
<BlogPost v-bind="post" />
而这实际上等价于:
<BlogPost :id="post.id" :title="post.title" />
15.4 Prop 校验#
Vue 组件可以更细致地声明对传入的 props 的校验要求。比如我们上面已经看到过的类型声明,如果传入的值不满足类型要求,Vue 会在浏览器控制台中抛出警告来提醒使用者。这在开发给其他开发者使用的组件时非常有用。
要声明对 props 的校验,你可以向 defineProps() 宏提供一个带有 props 校验选项的对象,例如:
defineProps({
// 基础类型检查
// (给出 `null` 和 `undefined` 值则会跳过任何类型检查)
propA: Number,
// 多种可能的类型
propB: [String, Number],
// 必传,且为 String 类型
propC: {
type: String,
required: true
},
// Number 类型的默认值
propD: {
type: Number,
default: 100
},
// 对象类型的默认值
propE: {
type: Object,
// 对象或数组的默认值
// 必须从一个工厂函数返回。
// 该函数接收组件所接收到的原始 prop 作为参数。
default(rawProps) {
return {
message: 'hello' }
}
},
// 自定义类型校验函数
propF: {
validator(value) {
// The value must match one of these strings
return ['success', 'warning', 'danger'].includes(value)
}
},
// 函数类型的默认值
propG: {
type: Function,
// 不像对象或数组的默认,这不是一个工厂函数。这会是一个用来作为默认值的函数
default() {
return 'Default function'
}
}
})
提示:
defineProps() 宏中的参数不可以访问 <script setup> 中定义的其他变量,因为在编译时整个表达式都会被移到外部的函数中。
一些补充细节:
- 所有 prop 默认都是可选的,除非声明了
required: true。 - 除
Boolean外的未传递的可选 prop 将会有一个默认值undefined。 Boolean类型的未传递 prop 将被转换为false。你应该为它设置一个default值来确保行为符合预期。- 如果声明了
default值,那么在 prop 的值被解析为undefined时,无论 prop 是未被传递还是显式指明的undefined,都会改为default值。
当 prop 的校验失败后,Vue 会抛出一个控制台警告 (在开发模式下)。
如果使用了基于类型的 prop 声明 ,Vue 会尽最大努力在运行时按照 prop 的类型标注进行编译。举例来说,defineProps<{ msg: string }> 会被编译为 { msg: { type: String, required: true }}。
15.5 Boolean 类型转换#
为了更贴近原生 boolean attributes 的行为,声明为 Boolean 类型的 props 有特别的类型转换规则。以带有如下声明的 <MyComponent> 组件为例:
defineProps({
disabled: Boolean
})
该组件可以被这样使用:
<!-- 等同于传入 :disabled="true" -->
<MyComponent disabled />
<!-- 等同于传入 :disabled="false" -->
<MyComponent />
当一个 prop 被声明为允许多种类型时,例如:
defineProps({
disabled: [Boolean, Number]
})
无论声明类型的顺序如何,Boolean 类型的特殊转换规则都会被应用。
15.6 单向数据流
所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。
另外,每次父组件更新后,所有的子组件中的 props 都会被更新到最新值,这意味着你不应该在子组件中去更改一个 prop。若你这么做了,Vue 会在控制台上向你抛出警告:
const props = defineProps(['foo'])
// 警告!prop 是只读的!
props.foo = 'bar'
导致你想要更改一个 prop 的需求通常来源于以下两种场景:
prop 被用于传入初始值;而子组件想在之后将其作为一个局部数据属性。在这种情况下,最好是新定义一个局部数据属性,从 props 上获取初始值即可:
const props = defineProps(['initialCounter']) // 计数器只是将 props.initialCounter 作为初始值 // 像下面这样做就使 prop 和后续更新无关了 const counter = ref(props.initialCounter)需要对传入的 prop 值做进一步的转换。在这种情况中,最好是基于该 prop 值定义一个计算属性:
const props = defineProps(['size']) // 该 prop 变更时计算属性也会自动更新 const normalizedSize = computed(() => props.size.trim().toLowerCase())
更改对象 / 数组类型的 props#
当对象或数组作为 props 被传入时,虽然子组件无法更改 props 绑定,但仍然可以更改对象或数组内部的值。这是因为 JavaScript 的对象和数组是按引用传递,而对 Vue 来说,禁止这样的改动虽然可能,但有很大的性能损耗,比较得不偿失。
这种更改的主要缺陷是它允许了子组件以某种不明显的方式影响父组件的状态,可能会使数据流在将来变得更难以理解。在最佳实践中,你应该尽可能避免这样的更改,除非父子组件在设计上本来就需要紧密耦合。在大多数场景下,子组件应该抛出一个事件来通知父组件做出改变。
16.组件事件
基础使用和vue2一样
16.1 触发与监听事件
在组件的模板表达式中,可以直接使用 $emit 方法触发自定义事件 (例如:在 v-on 的处理函数中):
<!-- MyComponent -->
<button @click="$emit('someEvent')">click me</button>
父组件可以通过 v-on (缩写为 @) 来监听事件:
<MyComponent @some-event="callback" />
同样,组件的事件监听器也支持 .once 修饰符:
<MyComponent @some-event.once="callback" />
像组件与 prop 一样,事件的名字也提供了自动的格式转换。注意这里我们触发了一个以 camelCase 形式命名的事件,但在父组件中可以使用 kebab-case 形式来监听。与 prop 大小写格式一样,在模板中我们也推荐使用 kebab-case 形式来编写监听器。
提示
和原生 DOM 事件不一样,组件触发的事件没有冒泡机制。你只能监听直接子组件触发的事件。平级组件或是跨越多层嵌套的组件间通信,应使用一个外部的事件总线,或是使用一个全局状态管理方案。
16.2 事件参数#
有时候我们会需要在触发事件时附带一个特定的值。举例来说,我们想要 <BlogPost> 组件来管理文本会缩放得多大。在这个场景下,我们可以给 $emit 提供一个额外的参数:
<button @click="$emit('increaseBy', 1)">
Increase by 1
</button>
然后我们在父组件中监听事件,我们可以先简单写一个内联的箭头函数作为监听器,此函数会接收到事件附带的参数:
<MyButton @increase-by="(n) => count += n" />
或者,也可以用一个组件方法来作为事件处理函数:
<MyButton @increase-by="increaseCount" />
该方法也会接收到事件所传递的参数:
function increaseCount(n) {
count.value += n
}
提示
所有传入
$emit()的额外参数都会被直接传向监听器。举例来说,$emit('foo', 1, 2, 3)触发后,监听器函数将会收到这三个参数值。
16.3 声明触发的事件
组件要触发的事件可以显式地通过 defineEmits() 宏来声明:
<script setup>
defineEmits(['inFocus', 'submit'])
</script>
这声明了一个组件可能触发的所有事件,还可以对事件的参数进行验证。同时,这还可以让 Vue 避免将它们作为原生事件监听器隐式地应用于子组件的根元素。
和 defineProps 类似,defineEmits 仅可用于 <script setup> 之中,并且不需要导入,它返回一个等同于 $emit 方法的 emit 函数。它可以被用于在组件的 <script setup> 中抛出事件,因为此处无法直接访问 $emit:
<script setup>
const emit = defineEmits(['inFocus', 'submit'])
function buttonClick() {
emit('submit')
}
</script>
defineEmits() 宏不能在子函数中使用。如上所示,它必须直接放置在 <script setup> 的顶级作用域下。
TypeScript 用户请参考:为组件 emits 标注类型
如果你显式地使用了 setup 函数而不是 <script setup>,则事件需要通过 emits 选项来定义,emit 函数也被暴露在 setup() 的上下文对象上:
export default {
emits: ['inFocus', 'submit'],
setup(props, ctx) {
ctx.emit('submit')
}
}
与 setup() 上下文对象中的其他属性一样,emit 可以安全地被解构:
export default {
emits: ['inFocus', 'submit'],
setup(props, {
emit }) {
emit('submit')
}
}
这个 emits 选项还支持对象语法,它允许我们对触发事件的参数进行验证:
<script setup>
const emit = defineEmits({
submit(payload) {
// 通过返回值为 `true` 还是为 `false` 来判断
// 验证是否通过
}
})
</script>
如果你正在搭配 TypeScript 使用 <script setup>,也可以使用纯类型标注来声明触发的事件:
<script setup lang="ts">
const emit = defineEmits<{
(e: 'change', id: number): void (e: 'update', value: string): void }>()
</script>
TypeScript 用户请参考:如何为组件所抛出事件标注类型
尽管事件声明是可选的,我们还是推荐你完整地声明所有要触发的事件,以此在代码中作为文档记录组件的用法。同时,事件声明能让 Vue 更好地将事件和透传 attribute 作出区分,从而避免一些由第三方代码触发的自定义 DOM 事件所导致的边界情况。
16.4 事件校验
和对 props 添加类型校验的方式类似,所有触发的事件也可以使用对象形式来描述。
要为事件添加校验,那么事件可以被赋值为一个函数,接受的参数就是抛出事件时传入 emit 的内容,返回一个布尔值来表明事件是否合法。
<script setup>
const emit = defineEmits({
// 没有校验
click: null,
// 校验 submit 事件
submit: ({
email, password }) => {
if (email && password) {
return true
} else {
console.warn('Invalid submit event payload!')
return false
}
}
})
function submitForm(email, password) {
emit('submit', {
email, password })
}
</script>
16.5 关于组件使用原生事件
这是我目前想到的,如果有错误,请指出
Vue3移除v-on.native修饰符,那如何在组件上触发原生事件呢?
其实可以利用属性继承,详细可以看17.1 Attributes 继承
注意要写根标签,因为这个事件只会继承在根标签,因为Vue3不强制写根标签,所以会出现多个标签,这时这个事件找不到根标签会发出警告,解决的方法在Attributes 继承里面说了
父组件App.vue
<template>
<span>父组件</span>
<br />
<Son @click="test"></Son>
</template>
<script setup>
import Son from './components/Son.vue';
function test() {
console.log('test');
}
</script>
子组件Son.vue
<template>
<div>
<span>子组件</span>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
</div>
</template>

16.6 vue3中事件总线
1.在vue3中,创建方法改成了createApp({}),prototype属性也被取消了,因此无法使用之前Vue.prototype.$bus = new Vue()的方式使用事务总线。
2.且在vue3中移除了 o n , on, on,off方法,因此官方推荐:事件总线模式可以被替换为使用外部的、实现了事件触发器接口的库,例如 mitt 或 tiny-emitter。
3.首先安装mitt:npm install --save mitt
4.在代码中使用:
import mitt from "mitt"
import {
createApp} from "vue"
const app = createApp({
})//正常配置
//挂载事务总线
app.config.globalProperties.$bus = new mitt()
//在组件A中使用事务总线触发某个动作
this.$bus.emit("EVENTTYPE");
//在组件B中监听动作的发生
this.$bus.on("EVENTTYPE",()=>{
console.log("EVENTTYPE发生了")})
边栏推荐
猜你喜欢
随机推荐
【Enterprise Architecture】Agile and Enterprise Architecture: Strategic Alliance
硬件工程师90天学习资料及笔记汇总20220730
原型和原型链
DataStream API(基础篇) 完整使用 (第五章)
【数据库架构】OLTP 和 OLAP:实际比较
Vivado时序约束中Tcl命令的对象及属性
【OAuth2】十九、OpenID Connect 动态客户端注册
Nvidia's gaming graphics card revenue plummets / Google data center explosion injures 3 people / iPhone battery percentage returns... More news today is here...
【API架构】使用 JSON API 的好处
【业务架构】价值链分析:提高客户价值和盈利能力
郭晶晶家的象棋私教,好家伙是个机器人
Spotify expresses its architectural design using the C4 model
PTA Exercise 2.1 Simple Calculator
iwemeta metaverse: Ali's first COO: how to build a sales force
爬虫-爬取某小说网站
浅析JWT安全问题
Basic concepts, structures, and classes of thread pools
日期类(暑假每日一题 19)
JWT: To own me is to have power
基于sklearn的决策树应用实战



![[OAuth2] 20. OAuth2 Extended Protocol PKCE](/img/ff/37f3be22bf209222cadaf5d11f951a.png)





