自定义事件

本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2021-05-31

定义自定义事件

在 Vue2 中,子组件与父组件进行通信,通常要定义事件给父组件,让父组件进行监听,就会使用到 this.$emit

有一种场景是,在一个拥有众多事件的“大”组件内,我们想知道该组件到底会触发哪些事件,影响什么业务,可能需要在文档内搜索 $emit 来查看和阅读代码,而这并不够优雅,如果多人开发,那么其他人不阅读完整的代码,就无法知道该组件到底会发出哪些事件,甚至有可能跟你定义了一样的事件去发出。

Vue3 提供了 emits 选项在组件上定义已发出的事件。

export default {
  emits: ['in-focus', 'submit']
}

这样管理起来不仅方便查看,也能更清楚的知道该组件会发出哪些事件。

建议定义所有发出的事件,以便更好地记录组件应该如何工作。需要注意的是,当在 emits 选项中定义了原生事件 (如 click) 时,将使用组件中的事件替代原生事件侦听器。

对于子组件中未被定义为组件触发的所有事件监听器,Vue 现在将把它们作为原生事件监听器添加到子组件的根元素中,所以在 Vue3 中移除了 v-on.native 修饰符。

验证抛出的事件

有些时候,当由其他开发人员需要使用已经定义好的事件时,并不能够明确的在当前组件代码内部就找到如何正确的“发出”事件,往往需要找到接受处才能完全明白。

Vue3 提供的 emits 选项可以像 Props 一样通过添加验证来让大家知道如何以正确的姿势“发出”事件。

export default {
  emits: {
    // 没有验证
    click: null,

    // 验证submit 事件 需要返回一个布尔值
    // 接受到的参数就是 payload
    submit: ({ email, password }) => {
      if (email && password) {
        return true
      }
      return false
    }
  },
  methods: {
    submitForm() {
      this.$emit('submit', { email, password })
    }
  }
}

当无法通过验证 return false 时,事件依然会被发出和接收,但是会在控制台中输出一个 warning。

[Vue warn]: Invalid event arguments: event validation failed for event "submit"

v-model参数

Vue2中v-model的一些限制

在 Vue2 中,当父子组件使用 v-model 双向数据绑定时,会有些许的不方便。

<!-- 在父组件使用的时候 -->
<child-input v-model="title" /> 

子组件的实现需要这样:

<template>
  <input 
    :value="value"
    @input="$emit('input', $event.target.value)"
  />
</template>
<script>
export default {
  props: ['value'], // 必须是value,上方也必须是 $emit('input')
}
</script>

因为 v-model 就是以下写法的语法糖:

<child-input :value="title"  @input="val => { title =val }"/> 

一个组件上的 v-model 会把 value 用作 prop 且把 input 用作 event,但是一些输入类型比如单选框和复选框按钮可能想使用 value prop 来达到不同的目的,就会受限,因为大多数情况下这些标签的值需要通过由checked属性来判断该元素的value是否能够作为单选框或复选框的“有效值”。

所以,在 Vue2.2+ 中新增了 model 选项了来解决这一弊病。

<template>
  <input
    type="checkbox"
    value="男"
    :checked="myChecked"
    @change="$emit('myChange', $event.target.checked)"
  />
</template>
<script>
export default {
  model: {
    prop: 'myChecked', // 1
    event: 'myChange',
  },
  props: ['myChecked'] // 2 这两处要与上面的 :checked="myChecked" 都使用 myChecked 保持一致
}
</script>

model 选项相当于定义了组件被使用并有v-model指令的时候会被渲染成何种样子。这个时候,

<!-- 这样的写法 -->
<my-checkbox v-model="foo" value="some value"></my-checkbox>

<!-- 就会被渲染成 -->
<my-checkbox
  :myChecked="foo"
  @myChange="val => { foo = val }"
  value="some value">
</my-checkbox>

所以上面代码 标记2 处,必须使用 model 选项定义的 myChecked 接受 Props,发出事件的时候也要使用 $emit('myChange')

同样 .sync 修饰符也有类似的处理:

<comp :foo.sync="bar"></comp>

<!-- 等同于 -->
<comp :foo="bar" @update:foo="val => { bar = val }"></comp>

comp 组件在需要更新父组件中的 bar 时,就需要使用 $emit('update:foo'),也需要使用Props接受 foo。

Vue3对v-model作出的改变

在 Vue2 中,一个组件上的 v-model 会把 value 用作 prop 且把 input 用作 event

而在 Vue3 中,一个组件上的 v-model 会把 modelValue 用作 prop 且把 update:modelValue 用作 event

而针对普通元素的v-model采用一样的行为:
1、text 和 textarea 元素使用 value property 和 input 事件
2、checkbox 和 radio 使用 checked property 和 change 事件;
3、select 字段将 value 作为 prop 并将 change 作为事件。

Vue3 使用向 v-model 传递参数来代替 model 选项。

<my-component v-model="bookTitle"></my-component>
<!-- 等同于 -->
<my-component :modelValue="bookTitle" @update:modelValue="val => { bookTitle = val }"></my-component>

<!-- 分割线 -->

<my-component v-model:title="bookTitle"></my-component>
<!-- 等同于 -->
<my-component :title="bookTitle" @update:title="val => { bookTitle = val }"></my-component>

v-model:title 可以理解为等同于将 modelValue 更名为 title 了。

<!-- my-component实现 -->
<template>
  <input
    type="text"
    :value="title"
    @input="$emit('update:title', $event.target.value)">
  />
</template>

<script>
export default {
  emits: ['update:title'],
  props: ['title']
}
</script>

Vue3移除了 .sync 修饰符,因此这也可以作为 .sync 修饰符的替代。

<ChildComponent :title.sync="pageTitle" />

<!-- 替换为 -->

<ChildComponent v-model:title="pageTitle" />

多个v-model绑定

通过利用以特定 prop 和 event 为目标的能力,我们现在可以在单个组件实例上创建多个 v-model 绑定。每个 v-model 将同步到不同的 prop,而不需要在组件中添加额外的选项。

<user-name
  v-model:first-name="firstName"
  v-model:last-name="lastName"
></user-name>
<template>
  <input 
    type="text"
    :value="firstName"
    @input="$emit('update:firstName', $event.target.value)"
  >

  <input
    type="text"
    :value="lastName"
    @input="$emit('update:lastName', $event.target.value)"
  >
</template>

<script>
export default {
  props: {
    firstName: String,
    lastName: String
  },
  emits: ['update:firstName', 'update:lastName'],
}
</script>

处理v-model修饰符

在 Vue2 中,v-model 有以下内置修饰符:

  • lazy: v-model 在每次 input 事件触发后将输入框的值与数据进行同步,使用 lazy 修饰符会转为在 change 事件之后进行同步,降低消耗。

  • number: 自动将用户的输入值转为数值类型

  • trim: 自动过滤用户输入的首尾空白字符

Vue3 提供了自定义修饰符的功能,将会更将方便使用,添加到组件 v-model 的修饰符将通过 modelModifiers prop 提供给组件。

以下代码定义了一个修饰符将用户的输入自动转为首字母大写:

<my-component v-model.capitalize="myText"></my-component>
<!-- my-component -->
<template>
  <input 
    type="text" 
    :value="modelValue"
    @input="emitValue"
  >
</template>

<script>
export default {
  props: {
    modelValue: String,
    modelModifiers: {
      default: () => ({})
    }
  },
  emits: ['update:modelValue'],
  
  // 当组件的 created 生命周期钩子触发时,modelModifiers prop 会包含 capitalize,且其值为 true
  // 因为 capitalize 被设置在了写为 v-model.capitalize="myText" 的 v-model 绑定上。
  created() {
    console.log(this.modelModifiers) // { capitalize: true }
  },
  methods: {
    emitValue(e) {
      let value = e.target.value
      if (this.modelModifiers.capitalize) { // 定义了 capitalize 修饰符
        value = value.charAt(0).toUpperCase() + value.slice(1)
      }
      this.$emit('update:modelValue', value)
    }
  }
}
</script>

对于带参数的 v-model 绑定,生成的 prop 名称将为 arg + "Modifiers"

<my-component v-model:description.capitalize="myText"></my-component>
export default {
  props: ['description', 'descriptionModifiers'],,
  emits: ['update:description'],
  
  methods: {
    emitValue(e) {
      let value = e.target.value
      if (this.descriptionModifiers.capitalize) {
        value = value.charAt(0).toUpperCase() + value.slice(1)
      }
      this.$emit('update:description', value)
    }
  }
}
</script>