文章目录
1. 事件基础示例Blog.vueBlogPost.vue 2. 触发与监听事件2.1 触发事件2.2 监听事件 3. 事件参数3.1 示例1Blog.vueBlogPost.vue 3.2 示例2(defineEmits)MyComponent.vueTest.vue 4. 声明触发的事件5. 事件校验示例1Blog.vueBlogPost.vue 示例2(defineEmits)MyComponent.vueTest.vue
1. 事件基础
子组件有时候需要与父组件进行交互,子组件需要通知父组件做一些事(比如:prop是单向数据量,子组件不应该直接修改prop绑定的值,而是应该抛出一个事件来通知父组件做出改变
)为了解决这个问题,组件实例提供了一个自定义事件系统,父组件可以通过v-on
或 @
来选择性地监听子组件上抛的事件
,就像监听原生 DOM 事件那样,当监听到子组件上抛出的事件时,就会执行对应事件绑定的监听函数 子组件可以通过调用内置的 $emit 方法
,通过传入事件名称来抛出一个事件子组件使用内置的 $emit 方法中,可以使用 $event
作为事件参数我们可以通过 emits 选项
来声明需要抛出的事件(emits声明选项不是必须的) 这声明了一个组件可能触发的所有事件,还可以对事件的参数进行验证还可以让 Vue 避免将它们作为原生事件监听器隐式地应用于子组件的根元素。 如果子组件中声明了emits:[‘click’]选项,然后在某个按钮下使用了$emit(‘click’)去触发click事件,则父组件中使用该子组件时,使用@click=‘handleClick’去监听click事件时,不会认为这是原生dom的点击事件,只有当子组件使用$emit(‘click’)触发click事件时,才会触发hanleClick函数。如果不加emits:[‘click’]选项,则只要点击就会触发handleClick事件。 示例
Blog.vue
<template> <div :style="{ fontSize: postFontSize + 'em' }"> <BlogPost v-for="post in posts" :key="post.id" :title="post.title" @enlarge-text="postFontSize += 0.1"/> </div></template><script >import BlogPost from './BlogPost.vue'export default { name: 'Blog', components: { BlogPost }, data() { return { posts: [ { id: 1, title: 'My journey with Vue' }, { id: 2, title: 'Blogging with Vue' }, { id: 3, title: 'Why Vue is so fun' } ], postFontSize: 1 } }}</script><style lang="scss"></style>
BlogPost.vue
<template> <div class="blog-post"> <h4>{{ title }}</h4> <button @click="$emit('enlarge-text')">Enlarge text</button> </div></template><script>export default { name: 'BlogPost', props: ['title'], // 这个emits声明选项不是必须的 emits: ['enlarge-text']}</script><style lang="scss"></style>
2. 触发与监听事件
2.1 触发事件
在组件的模板表达式中,可以直接使用 $emit 方法触发自定义事件
$emit() 方法在组件实例上也同样以 this.$emit() 的形式可用:
<templte><button @click="$emit('someEvent')">click me</button></template><script>export default { methods: { submit() { this.$emit('someEvent') } }}</script>
2.2 监听事件
父组件可以通过 v-on (缩写为 @) 来监听事件
组件的事件监听器也支持 .once 修饰符
- 触发事件时建议使用camelCase 形式
(驼峰命名,首字母小写),父组件监听是可以使用kebab-case 形式
(短横线分隔,推荐)
组件触发的事件没有冒泡机制,你只能监听直接子组件触发的事件
。
外部的事件总线
或者使用一个全局状态管理方案
。 <MyComponent @some-event="callback" /><MyComponent @some-event.once="callback" />
3. 事件参数
有时候会需要在触发事件时附带一个特定的值(事件参数)可以写一个内联的箭头函数作为监听器,此函数会接收到事件附带的参数也可以用一个组件方法来作为事件处理函数,该方法也会接收到事件所传递的参数所有传入 $emit() 的额外参数都会被直接传向监听器。 举例来说,$emit(‘foo’, 1, 2, 3) 触发后,监听器函数将会收到这三个参数值。3.1 示例1
Blog.vue
<template> {{ count }} <BlogPost @increase-by="(n) => count += n" /> <BlogPost @increase-by="increaseCount" /></template><script >import BlogPost from './BlogPost.vue'export default { name: 'Blog', components: { BlogPost }, data() { return { count: 0 } }, methods: { increaseCount(n,m,e) { console.log(n,m,e); // 1, 2, PointerEvent {isTrusted: true, _vts: 1682821628803, pointerId: 1, width: 1, height: 1, …} this.count += n } }}</script><style lang="scss"></style>
BlogPost.vue
<template> <div class="blog-post"> <button @click="$emit('increaseBy', 1, 2, $event)"> Increase by 1 </button> </div></template><script>export default { name: 'BlogPost',}</script><style lang="scss"></style>
3.2 示例2(defineEmits)
子组件可以触发自定义事件,父组件使用该子组件时,可以监听该组件的事件,并为监听的事件绑定事件处理函数, 当该子组件触发了该事件时,父组件对应的事件处理函数就会执行。MyComponent.vue
<template> <!-- 在组件的模板表达式中,可以直接使用 $emit 方法触发自定义事件 --> <!-- 可以通过 v-on (缩写为 @) 来监听事件, 即:@click --> <!-- 这里可以使用驼峰命名形式触发的事件名称, 而在父组件中使用短横线命名形式监听 --> <!-- 传入3个参数, 则在父组件中监听函数中, 也应至少有3个参数, 否则按参数个数赋值,也可以在这里传个对象过去,就只需要一个参数了, 并且可以使用$event, 将当前事件传过去 --> <!-- 参数也可以使用模板中拿到的参数, 比如v-for循环里拿到的每一项数据 --> <button v-on:click="$emit('doUpdate',$event,1,2)">触发doUpdate事件</button> <br/> <button v-on:click="$emit('refresh', {name:'zzhua',age:Math.floor(Math.random() * 20)})">触发refresh事件</button> 子组件中person: {{ person }}<br/> <!-- 可以使用事件修饰符, once表示该事件只会触发1次 --> <button v-on:click.once="handleClick()">触发click事件</button> </template><script lang="ts" setup> import { ref,reactive,onUpdated } from 'vue' /* 1. defineEmits() 宏不能在子函数中使用。它必须直接放置在 <script setup> 的顶级作用域下 */ /* 2. 显式地使用了 setup 函数而不是 <script setup>,则事件需要通过 emits 选项来定义,emit 函数也被暴露在 setup() 的上下文对象上 */ const emit = defineEmits(['doUpdate']) /* 可以不需要定义doUpdate, 但建议定义doUpdate, 以此来声明此组件可以被触发的事件 */ function handleClick () { console.log('click'); emit('doUpdate',e,3,4) /* 使用emit,手动触发doUpdate事件 */ } const props = defineProps(['person']) onUpdated(()=>{ console.log('---组件渲染更新了---') /* 当通过触发子组件事件, 父组件监听到该组件的该事件, 修改了person的ref值, 从而组件会更新 */ })</script><style lang="scss"> button { margin: 5px; }</style>
Test.vue
<template> <!-- 可以使用短横线形式监听当前这个组件的事件,并指定事件处理函数 --> <my-component :person="person" v-on:do-update="handleUpdate" @refresh="handleRefresh" ></my-component><br/> 父组件中person: {{ person }}</template><script lang="ts" setup> import { ref,reactive,computed } from 'vue' import MyComponent from './MyComponent.vue'; const person = ref({ name: 'demoName', age: NaN}) function handleUpdate(event,payload1,payload2) { console.log(event); // PointerEvent {isTrusted: true, _vts: 1678062697219, pointerId: 1, width: 1, height: 1, …} console.log(payload1, payload2); } function handleRefresh( p) { console.log(p); // {name: 'zzhua', age: 18} person.value = p }</script><style lang="scss"> </style>
4. 声明触发的事件
组件可以显式地通过 emits 选项来声明它要触发的事件这个 emits 选项还支持对象语法,它允许我们对触发事件的参数进行验证尽管事件声明是可选的,我们还是推荐完整地声明所有要触发的事件
。同时,事件声明能让 Vue 更好地将事件和透传 attribute 作出区分,从而避免一些由第三方代码触发的自定义 DOM 事件所导致的边界情况。 如果一个原生事件的名字 (例如 click) 被定义在 emits 选项中,则监听器只会监听组件触发的 click 事件而不会再响应原生的 click 事件。 5. 事件校验
和对 props 添加类型校验的方式类似,所有触发的事件可以使用字符串
,也可以使用对象形式
要为事件添加校验,那么事件可以被赋值为一个函数
,接受的参数就是抛出事件时传入 this.$emit 的内容,返回一个布尔值来表明事件是否合法
示例1
Blog.vue
<template> {{ count }} <BlogPost @submit="handleSubmit" /></template><script >import BlogPost from './BlogPost.vue'export default { name: 'Blog', components: { BlogPost }, data() { return { count: 0 } }, handleSubmit(payload) { console.log(payload); }}</script><style lang="scss"></style>
BlogPost.vue
<template> <div class="blog-post"> <button @click="$emit('submit', {password:'123456'})"> submit </button> </div></template><script>export default { name: 'BlogPost', 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 }) } }}</script><style lang="scss"></style>
示例2(defineEmits)
MyComponent.vue
<template> <button @click="submitForm(123,456)">触发submitForm事件</button></template><script lang="ts" setup>import { ref, reactive } from 'vue'const emit = defineEmits({ // 没有校验 click: null, // 校验 submit 事件 submit: ({ email, password }) => { if (email && password) { console.log(email, password); return true } else { console.warn('Invalid submit event payload!') return false } }})function submitForm(email, password) { emit('submit', { email, password })}</script><style lang="scss">button { margin: 5px;}</style>
Test.vue
<template> <MyComponent></MyComponent></template><script lang="ts" setup> import { ref,reactive,computed } from 'vue' import MyComponent from './MyComponent.vue';</script><style lang="scss"> </style>