本文共--字 阅读约--分钟 | 浏览: -- 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"
在 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。
在 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" />
通过利用以特定 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>
在 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>