父子组件事件监听
上一节中提到的<BlogPost>
组件。有时候它需要与父组件进行交互。例如,要在此处实现无障碍访问的需求,将博客文章的文字能够放大,而页面的其余部分仍使用默认字号。在父组件中,可以添加一个 postFontSize
ref引用数据来实现动态控制字体大小的效果:
// 父组件中的属性定义
const posts = ref([
/* ... */
])
const postFontSize = ref(1) // 字体大小默认为1em
在父组件模板中用它来控制所有博客文章的字体大小:
<!-- 父组件中的模板 -->
<div :style="{ fontSize: postFontSize + 'em' }">
<BlogPost
v-for="post in posts"
:key="post.id"
:title="post.title"
/>
</div>
然后,给 <BlogPost>
子组件添加一个按钮:
<!-- BlogPost.vue, 省略了 <script> -->
<template>
<div class="blog-post">
<h4>{{ title }}</h4>
<button>Enlarge text</button>
</div>
</template>
$emit方法触发自定义监听事件
这个按钮目前还没有做任何事情,我们想要点击这个按钮来告诉父组件它应该放大所有博客文章的文字。要解决这个问题,组件实例提供了一个自定义事件系统。父组件可以通过 v-on
或 @
来选择性地监听子组件上抛的事件,就像监听原生 DOM 事件那样:
<script setup>
// 定义一个事件处理函数
function changeFontsize(){
postFontSize.value += 0.1
}
</script>
<template>
<BlogPost
...
<!-- 父组件用于监听子组件中抛出的enlarge-text事件 -->
<!-- 为事件监听器@enlarge-text(v-on:enlarge)设置事件响应处理函数(内联函数):postFontSize += 0.1 -->
@enlarge-text="postFontSize += 0.1"
<!-- 传递一个事件处理函数-->
<!-- @enlarge-text="changeFontSize"-->
/>
</template>
子组件可以通过调用内置的 $emit
方法,通过传入事件名称来抛出enlarge-text
事件,因为有了 @enlarge-text="postFontSize += 0.1"
的事件监听器,父组件会接收到事件,则会迅速更新 postFontSize
的值:
<!-- BlogPost.vue, 省略了 <script> -->
<template>
<div class="blog-post">
<h4>{{ title }}</h4>
<!-- 子组件通过$emit方法触发enlarge-text自定义事件 -->
<button @click="$emit('enlarge-text')">Enlarge text</button>
</div>
</template>
和原生DOM事件不同,组件触发的事件没有冒泡机制。组件只能监听直接子组件触发的事件。平级组件或是跨越多层嵌套的组件间通信,应使用外部事件总线,或是使用全局状态管理方案。
defineEmits()
注册自定义监听事件
在组合式API中,则可以通过 defineEmits
宏来声明需要抛出的事件:
<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
defineEmits(['enlarge-text'])
</script>
这声明了一个组件可能触发的所有事件,还可以对事件的参数进行验证。同时,这还可以让 Vue 避免将它们作为原生事件监听器隐式地应用于子组件的根元素。
与 defineProps
宏类似,defineEmits
宏仅可用于 <script setup>
之中,并且不需要导入,它返回一个等同于 $emit
方法的 emit
函数。它可以被用于在组件的 <script setup>
中抛出事件,因为此处无法直接访问 $emit
:
<script setup>
defineProps(['title'])
const emit = defineEmits(['enlarge-text']) // 使用defineEmits宏注册事件
const emitCustomEvent = ()=>{
emit('enlarge-text') // 抛出事件
}
</script>
<!-- BlogPost.vue, 省略了 <script> -->
<template>
<div class="blog-post">
<h4>{{ title }}</h4>
<!-- 子组件通过$emit方法触发enlarge-text自定义事件 -->
<button @click="emitCustomEvent">Enlarge text</button>
</div>
在选项式API中,可以通过 emits
选项定义组件会抛出的事件。可以从 setup()
函数的第二个参数,即 setup 上下文对象ctx上访问到 emit
函数:
export default {
emits: ['enlarge-text'], //注册事件
setup(props, ctx) {
ctx.emit('enlarge-text') //抛出事件
}
}
defineEmits()函数参数对象中的选项配置——自定义事件校验
与对 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>