Skip to content

Vue 事件

在 Vue 中,父组件通过 props 向子组件传递数据,但子组件如何将信息传递回父组件呢?答案是使用自定义事件。子组件可以触发 (emit) 一个事件,而父组件可以像监听原生 DOM 事件一样监听 (listen) 这个事件。

触发与监听事件

子组件可以使用内置的 $emit 方法,通过传入事件名称来触发一个事件。

MyComponent.vue (子组件)

vue
<script setup>
// 使用 defineEmits 宏来声明该组件会触发的事件
const emit = defineEmits(['someEvent'])

function handleClick() {
  // 触发 'someEvent' 事件
  emit('someEvent')
}
</script>

<template>
  <button @click="handleClick">Click me</button>
</template>

父组件可以使用 v-on (简写为 @) 来监听子组件触发的事件。

App.vue (父组件)

vue
<script setup>
import MyComponent from './MyComponent.vue'

function onSomeEvent() {
  console.log('someEvent was emitted!')
}
</script>

<template>
  <MyComponent @some-event="onSomeEvent" />
</template>

注意,与 prop 的命名转换类似,事件名称也存在自动的大小写转换。因为 HTML attribute 是大小写不敏感的,所以我们推荐使用 kebab-case (短横线分隔) 的事件监听器。如果在子组件中触发的是驼峰式 (camelCase) 的事件 someEvent,在父组件中可以用 some-event 来监听。

传递参数

有时,在触发事件时向其传递特定的数据是很有用的。例如,我们可能想让 <BlogPost> 组件告诉父组件这篇博文应该放大多少字号。

BlogPost.vue (子组件)

vue
<script setup>
const emit = defineEmits(['enlarge-text'])

function enlargeText() {
  // 触发事件并传递一个值
  emit('enlarge-text', 0.1)
}
</script>

<template>
  <button @click="enlargeText">Enlarge text</button>
</template>

然后,当父组件监听这个事件时,可以通过一个内联的箭头函数来接收这个值。这个值会作为该函数的第一个参数。

App.vue (父组件)

vue
<script setup>
import { ref } from 'vue'
import BlogPost from './BlogPost.vue'

const postFontSize = ref(1)
</script>

<template>
  <div :style="{ fontSize: postFontSize + 'em' }">
    <BlogPost @enlarge-text="(amount) => postFontSize += amount" />
  </div>
</template>

如果你希望事件处理器是一个方法,那么被传递的参数会作为该方法的第一个参数传入。

vue
<script setup>
// ...
function onEnlargeText(amount) {
  postFontSize.value += amount
}
</script>

<template>
  <BlogPost @enlarge-text="onEnlargeText" />
</template>

事件校验

与 props 类似,我们也可以对触发的事件进行校验。要添加校验,事件需要被定义为一个对象,而不是字符串数组。

vue
<script setup>
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() {
  emit('submit', { email: 'user@example.com', password: '123' })
}
</script>

如果校验失败,Vue 会在控制台打印一个警告信息。

v-model 与组件

自定义事件也可以用于创建支持 v-model 的自定义输入组件。一个组件上的 v-model 默认会使用 modelValue 作为 prop 和 update:modelValue 作为事件。我们可以通过在子组件中实现这两点来让它支持 v-model

CustomInput.vue

vue
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

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

现在,父组件就可以在 CustomInput 上使用 v-model 了。

App.vue

vue
<script setup>
import { ref } from 'vue'
import CustomInput from './CustomInput.vue'

const searchText = ref('hello')
</script>

<template>
  <CustomInput v-model="searchText" />
  <p>{{ searchText }}</p>
</template>