触发与监听事件

在组件的模板表达式中,可以直接使用$emit方法触发自定义事件:

  1. <!-- MyComponent -->
  2. <button @click="$emit('someEvent')">click me</button>

$emit事件的时候需要传递一个「自定义事件名称」,事件名推荐使用驼峰 camelCase 的形式。

$emit()方法在组件实例上也同样以this.$emit()的形式可用:

  1. export default {
  2. methods: {
  3. submit() {
  4. this.$emit('someEvent')
  5. }
  6. }
  7. }

父组件通过v-on或者简写@进行监听子组件 emit 来的自定义事件:

  1. <MyComponent @some-event="callback" />

父组件在监听事件的时候推荐使用 kebab-case 的方式,因为这符合 HTML 的规范!

事件参数

子组件在通过$emit一个自定义事件的时候,可以向父组件传递参数:

  1. <button @click="$emit('increaseBy', 1)">
  2. Increase by 1
  3. </button>

然后父组件可以接收到子组件传递来的参数:

  1. <MyButton @increase-by="increaseCount" />
  1. export default{
  2. methods: {
  3. increaseCount(n) {
  4. this.count += n
  5. }
  6. }
  7. }

如果子组件想传递多个参数,我们可以在事件名后面依次传递:

  1. <!-- 子组件 emit 事件 -->
  2. <button @click="$emit('increaseBy', 1, 2, 3, 4, 5)">
  3. Increase by 1
  4. </button>
  1. // 父组件接收
  2. export default{
  3. methods: {
  4. increaseCount(...args) {
  5. console.log(args); // [1, 2, 3, 4, 5]
  6. }
  7. }
  8. }

但是你应该避免这么操作,而是使用对象去传递多个参数,这会让你的代码更加好维护。

  1. <!-- 子组件 emit 事件 -->
  2. <button @click="$emit('increaseBy', {num1: 1, num2: 2, num3:3 })">
  3. Increase by 1
  4. </button>

父组件可以更好的接收:

  1. // 父组件接收
  2. export default{
  3. methods: {
  4. increaseCount({ num1, num2, num3 }) {
  5. console.log(num1, num2, num3); // 1, 2, 3
  6. }
  7. }
  8. }

声明触发的事件

Vue3 新增了emits属性来注册需要 emit 出去的事件:

  1. export default {
  2. emits: ['inFocus', 'submit'],
  3. methods:{
  4. onFocus(){
  5. this.$emit('inFocus');
  6. },
  7. onSubmit(){
  8. this.$emit('submit');
  9. }
  10. }
  11. }

这是为了更好的记录组件的 emit 流程,而不再需要去代码文件里翻找!

如果子组件想要 emit 一个原生的事件,例如下面这个示例:

  1. <template>
  2. <div>
  3. <!-- 监听子组件传递来的原生事件 -->
  4. <my-counter @click="getCount" />
  5. </div>
  6. </template>
  7. <script>
  8. import MyCounter from "./components/MyCounter.vue";
  9. export default {
  10. name: "App",
  11. components: {
  12. MyCounter
  13. },
  14. methods: {
  15. // 打印子组件传递来的值
  16. getCount(count) {
  17. console.log(count);
  18. }
  19. }
  20. };
  21. </script>
  1. <template>
  2. <div>
  3. <h1>{{ count }}</h1>
  4. <my-button btn-type="primary" @click="setCount">+</my-button>
  5. </div>
  6. </template>
  7. <script>
  8. import MyButton from "./MyButton.vue";
  9. export default {
  10. name: "MyCounter",
  11. components: {
  12. MyButton
  13. },
  14. data() {
  15. return {
  16. count: 0
  17. };
  18. },
  19. methods: {
  20. setCount() {
  21. // $emit 出一个 click 事件
  22. this.count++;
  23. this.$emit("click", this.count);
  24. }
  25. }
  26. };
  27. </script>

屏幕录制2023-05-17 16.10.15.gif
结果你会发现,无论是点击页面中<button>元素还是点击<h1>元素,都会打印出事件对象event
你可能会有一个疑问?代码中明明打印的是this.$emit("click", this.count);出来的count属性啊,为啥会打印出event对象呢?
这其实和我们前面说过的「透传 attrs」有关系,我们调用子组件的时候绑定了@click事件,但是子组件没有注册这个原生的事件,就导致了 click 事件绑定到了<MyCounter>组件的根元素上,所以无论你点击页面中的<button>元素还是点击<h1>元素都产生了「事件冒泡」,父组件直接监听到了所以就会打印出event对象!!!

如果要想解决这个问题,你需要在子组件内部通过emits注册一下你要$emit出去的原生事件,这样 Vue 就能够更好地将事件和透传 attribute 作出区分,从而避免一些由第三方代码触发的自定义 DOM 事件所导致的边界情况。

  1. export default {
  2. name: "MyCounter",
  3. components: {
  4. MyButton
  5. },
  6. emits: ["click"],
  7. data() {
  8. return {
  9. count: 0
  10. };
  11. },
  12. methods: {
  13. setCount() {
  14. this.count++;
  15. this.$emit("click", this.count);
  16. }
  17. }
  18. };

屏幕录制2023-05-17 16.18.53.gif :::warning ⚠️ 注意
如果你是 Vue2 的版本,同样的问题,你需要通过修饰符.navtive去解决,例如:<my-counter @click.navtive="getCount" />
通过.navtive来描述代替原生事件!!! :::

事件校验

和对 props 添加类型校验的方式类似,所有触发的事件也可以使用对象形式来描述。
要为事件添加校验,那么事件可以被赋值为一个函数,接受的参数就是抛出事件时传入this.$emit的内容,返回一个布尔值来表明事件是否合法。

  1. export default {
  2. name: "MyForm",
  3. // emits: ["submit"],
  4. emits: {
  5. submit: ({ username, password }) => {
  6. if (username && password) {
  7. console.log("Emit successfully!!!");
  8. return true;
  9. }
  10. console.log("Faild to emit!!!")
  11. return false;
  12. }
  13. },
  14. data() {
  15. return {
  16. username: "",
  17. password: ""
  18. };
  19. },
  20. methods: {
  21. submitMyForm(e) {
  22. e.preventDefault();
  23. this.$emit("submit", {
  24. username: this.username,
  25. password: this.password
  26. });
  27. }
  28. }
  29. };

以上案例,当username && password不通过的时候,父组件仍然能接收到事件和事件参数,只是控制台会进行报警告而已。