上一篇讲到了props的使用。今天讲一讲组件事件以及我们做项目常常遇到的问题.
Vue3 从入门到放弃 (第四篇.Props使用)_Meta.Qing的博客-CSDN博客
在组件的模板表达式中,可以直接使用 $emit
方法触发自定义事件 (例如:在 v-on
的处理函数中):
- <button @click="$emit('someEvent')">click mebutton>
父组件可以通过 v-on
(缩写为 @
) 来监听事件:
<MyComponent @some-event="callback" />
同样,组件的事件监听器也支持 .once
修饰符:
<MyComponent @some-event.once="callback" />
像组件与 prop 一样,事件的名字也提供了自动的格式转换。注意这里我们触发了一个以 camelCase 形式命名的事件,但在父组件中可以使用 kebab-case 形式来监听。与 prop 大小写格式prop 大小写格式prop 大小写格式一样,在模板中我们也推荐使用 kebab-case 形式来编写监听器。
提示:和原生 DOM 事件不一样,组件触发的事件没有冒泡机制。你只能监听直接子组件触发的事件。平级组件或是跨越多层嵌套的组件间通信,应使用一个外部的事件总线,或是使用一个全局状态管理方案全局状态管理方案全局状态管理方案。
有时候我们会需要在触发事件时附带一个特定的值。举例来说,我们想要
组件来管理文本会缩放得多大。在这个场景下,我们可以给 $emit
提供一个额外的参数:
- <button @click="$emit('increaseBy', 1)">
- Increase by 1
- </button>
然后我们在父组件中监听事件,我们可以先简单写一个内联的箭头函数作为监听器,此函数会接收到事件附带的参数:
<MyButton @increase-by="(n) => count += n" />
或者,也可以用一个组件方法来作为事件处理函数:
<MyButton @increase-by="increaseCount" />
该方法也会接收到事件所传递的参数:
- function increaseCount(n) {
- count.value += n
- }
提示:所有传入 $emit()
的额外参数都会被直接传向监听器。举例来说,$emit('foo', 1, 2, 3)
触发后,监听器函数将会收到这三个参数值。
组件要触发的事件可以显式地通过 defineEmits()defineEmits()defineEmits() 宏来声明:
- defineEmits(['inFocus', 'submit'])
我们在 中使用的
$emit
方法不能在组件的 部分中使用,但
defineEmits()
会返回一个相同作用的函数供我们使用:
- const emit = defineEmits(['inFocus', 'submit'])
-
- function buttonClick() {
- emit('submit')
- }
defineEmits()
宏不能在子函数中使用。如上所示,它必须直接放置在 的顶级作用域下。
如果你显式地使用了 setup
函数而不是 ,则事件需要通过 emits 选项来定义,
emit
函数也被暴露在 setup()
的上下文对象上:
- export default {
- emits: ['inFocus', 'submit'],
- setup(props, ctx) {
- ctx.emit('submit')
- }
- }
与 setup()
上下文对象中的其他属性一样,emit
可以安全地被解构:
- export default {
- emits: ['inFocus', 'submit'],
- setup(props, { emit }) {
- emit('submit')
- }
- }
这个 emits
选项还支持对象语法,它允许我们对触发事件的参数进行验证:
- const emit = defineEmits({
- submit(payload) {
- // 通过返回值为 `true` 还是为 `false` 来判断
- // 验证是否通过
- }
- })
和对 props 添加类型校验的方式类似,所有触发的事件也可以使用对象形式来描述。
要为事件添加校验,那么事件可以被赋值为一个函数,接受的参数就是抛出事件时传入 emit
的内容,返回一个布尔值来表明事件是否合法。
- 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 })
- }
v-model
使用#自定义事件可以用于开发支持 v-model
的自定义表单组件。回忆一下 v-model
在原生元素上的用法:
template
"searchText" />
上面的代码其实等价于下面这段 (编译器会对 v-model
进行展开):
- <input
- :value="searchText"
- @input="searchText = $event.target.value"
- />
而当使用在一个组件上时,v-model
会被展开为如下的形式:
- <CustomInput
- :modelValue="searchText"
- @update:modelValue="newValue => searchText = newValue"
- />
要让这个例子实际工作起来,
组件内部需要做两件事:
input
元素的 value
attribute 绑定到 modelValue
propinput
元素上触发 update:modelValue
事件这里是相应的代码:
- <script setup>
- defineProps(['modelValue'])
- defineEmits(['update:modelValue'])
- script>
-
- <template>
- <input
- :value="modelValue"
- @input="$emit('update:modelValue', $event.target.value)"
- />
- template>
现在 v-model
也可以在这个组件上正常工作了:
<CustomInput v-model="searchText" />
另一种在组件内实现 v-model
的方式是使用一个可写的,同时具有 getter 和 setter 的计算属性。get
方法需返回 modelValue
prop,而 set
方法需触发相应的事件:
- <script setup>
- import { computed } from 'vue'
-
- const props = defineProps(['modelValue'])
- const emit = defineEmits(['update:modelValue'])
-
- const value = computed({
- get() {
- return props.modelValue
- },
- set(value) {
- emit('update:modelValue', value)
- }
- })
- script>
-
- <template>
- <input v-model="value" />
- template>
v-model
的参数#默认情况下,v-model
在组件上都是使用 modelValue
作为 prop,并以 update:modelValue
作为对应的事件。我们可以通过给 v-model
指定一个参数来更改这些名字:
<MyComponent v-model:title="bookTitle" />
在这个例子中,子组件应声明一个 title
prop,并通过触发 update:title
事件更新父组件值:
- <script setup>
- defineProps(['title'])
- defineEmits(['update:title'])
- script>
-
- <template>
- <input
- type="text"
- :value="title"
- @input="$emit('update:title', $event.target.value)"
- />
- template>
v-model
绑定#利用刚才在 v-model 参数 小节中学到的技巧,我们可以在一个组件上创建多个 v-model
双向绑定,每一个 v-model
都会同步不同的 prop:
- <UserName
- v-model:first-name="first"
- v-model:last-name="last"
- />
- defineProps({
- firstName: String,
- lastName: String
- })
-
- defineEmits(['update:firstName', 'update:lastName'])
-
- <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>
v-model
修饰符#在学习输入绑定时,我们知道了 v-model
有一些内置的修饰符,例如 .trim
,.number
和 .lazy
。在某些场景下,你可能想要一个自定义组件的 v-model
支持自定义的修饰符。
我们来创建一个自定义的修饰符 capitalize
,它会自动将 v-model
绑定输入的字符串值第一个字母转为大写:
<MyComponent v-model.capitalize="myText" />
组件的 v-model
上所添加的修饰符,可以通过 modelModifiers
prop 在组件内访问到。在下面的组件中,我们声明了 modelModifiers
这个 prop,它的默认值是一个空对象:
- <script setup>
- const props = defineProps({
- modelValue: String,
- modelModifiers: { default: () => ({}) }
- })
-
- defineEmits(['update:modelValue'])
-
- console.log(props.modelModifiers) // { capitalize: true }
- script>
-
- <template>
- <input
- type="text"
- :value="modelValue"
- @input="$emit('update:modelValue', $event.target.value)"
- />
- template>
注意这里组件的 modelModifiers
prop 包含了 capitalize
且其值为 true
,因为它在模板中的 v-model
绑定上被使用了。
有了 modelModifiers
这个 prop,我们就可以在原生事件侦听函数中检查它的值,然后决定触发的自定义事件中要向父组件传递什么值。在下面的代码里,我们就是在每次 元素触发
input
事件时将值的首字母大写:
- const props = defineProps({
- modelValue: String,
- modelModifiers: { default: () => ({}) }
- })
-
- const emit = defineEmits(['update:modelValue'])
-
- function emitValue(e) {
- let value = e.target.value
- if (props.modelModifiers.capitalize) {
- value = value.charAt(0).toUpperCase() + value.slice(1)
- }
- emit('update:modelValue', value)
- }
-
- <template>
- <input type="text" :value="modelValue" @input="emitValue" />
- template>
对于又有参数又有修饰符的 v-model
绑定,生成的 prop 名将是 arg + "Modifiers"
。举例来说:
<MyComponent v-model:title.capitalize="myText">
相应的声明应该是:
- const props = defineProps(['title', 'titleModifiers'])
- defineEmits(['update:title'])
-
- console.log(props.titleModifiers) // { capitalize: true }
我们继续上节的例子: 我们在src/components目录下,新建SearchButton.vue,加入以下代码.
- <template>
- <div>
- <input
- :value="modelValue"
- @input="$emit('update:modelValue', $event.target.value)"
- />
- div>
- template>
-
- <script setup>
- defineProps(['modelValue'])
- defineEmits(['update:modelValue'])
-
- script>
-
- <style>
- style>
然后在src/views/Home/HomeIndex.vue,覆盖以下代码
-
- // 导入头部组件
- import MetaHeader from '../../components/MetaHeader.vue';
- import SearchButton from '../../components/SearchButton.vue';
-
- import { reactive } from 'vue'
-
- const state = reactive({ searchText: '' })
-
-
-
- <template>
-
- <meta-header>meta-header>
-
- <h1>我们第一个vue3应用h1>
- <div>
- <h3>1.事件演示:h3>
- <search-button v-model="state.searchText">search-button>
- <p>搜索结果: {{ state.searchText }}p>
- div>
- template>
-
-
-
运行npm run dev,打开浏览器输入http://127.0.0.1:5173/#/