Skip to content

События компонентов

Подразумевается, что вы уже изучили и разобрались с разделом Основы компонентов. Если нет — прочитайте его сначала.

Отправка и прослушивание событий

Компонент может генерировать пользовательские события непосредственно в выражениях шаблона (например, в обработчике v-on), используя встроенный метод $emit:

template
<!-- MyComponent -->
<button @click="$emit('someEvent')">нажми на меня</button>

Метод $emit() также доступен в экземпляре компонента как this.$emit():

js
export default {
  methods: {
    submit() {
      this.$emit('someEvent')
    }
  }
}

Родительский компонент может прослушать его, используя v-on:

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

Модификатор .once также поддерживается для слушателей событий компонента:

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

Как компоненты и входные параметры, имена событий обеспечивают автоматическое преобразование регистра. Обратите внимание, что мы испустили событие в регистре camelCase, но можем прослушать его с помощью слушателя в регистре kebab-case в родительском компоненте. Как и в случае с регистрацией входных параметров, мы рекомендуем использовать kebab-case регистр для прослушивания событий в шаблонах.

Совет

В отличие от собственных событий DOM, события, испускаемые компонентами, не всплывают. Вы можете слушать только события, испускаемые непосредственно дочерним компонентом. Если необходимо взаимодействие между родственными или глубоко вложенными компонентами, используйте внешнюю шину событий или глобальное управление состоянием.

Аргументы события

Иногда бывает полезно передать определенное значение вместе с событием. Например, мы можем захотеть, чтобы компонент <BlogPost> отвечал за то, насколько увеличить текст. В таких случаях мы можем передать $emit дополнительные аргументы, чтобы предоставить это значение:

template
<button @click="$emit('increaseBy', 1)">
  Увеличить на 1
</button>

Затем, когда мы прослушиваем событие в родителе, мы можем использовать встроенную стрелочную функцию в качестве слушателя, что позволит нам получить доступ к аргументу события:

template
<MyButton @increase-by="(n) => count += n" />

Или, если обработчик события является методом:

template
<MyButton @increase-by="increaseCount" />

Затем значение будет передано в качестве первого параметра этого метода:

js
methods: {
  increaseCount(n) {
    this.count += n
  }
}
js
function increaseCount(n) {
  count.value += n
}

Совет

Все дополнительные аргументы, переданные в $emit() после имени события, будут переданы слушателю. Например, при $emit('foo', 1, 2, 3) функция обработчика события получит три аргумента.

Определение пользовательских событий

События генерируемые компонентом можно объявить с помощью defineEmits()опции emits:

vue
<script setup>
defineEmits(['inFocus', 'submit'])
</script>

Метод $emit, который мы использовали в <template> недоступен в разделе <script setup> компонента, но defineEmits() возвращает эквивалентную функцию, которую мы можем использовать вместо него:

vue
<script setup>
const emit = defineEmits(['inFocus', 'submit'])

function buttonClick() {
  emit('submit')
}
</script>

Макрос defineEmits() нельзя использовать внутри функции, он должен быть помещен непосредственно в <script setup>, как в примере выше.

Если вы используете явную функцию setup вместо <script setup> события должны быть объявлены с помощью опции emits, а функция emit доступна в контексте setup():

js
export default {
  emits: ['inFocus', 'submit'],
  setup(props, ctx) {
    ctx.emit('submit')
  }
}

Как и другие свойства контекста setup(), emit можно безопасно деструктурировать:

js
export default {
  emits: ['inFocus', 'submit'],
  setup(props, { emit }) {
    emit('submit')
  }
}
js
export default {
  emits: ['inFocus', 'submit']
}

Опция emits также поддерживает синтаксис объекта, что позволяет нам выполнять проверку полезной нагрузки пользовательских событий во время выполнения:

vue
<script setup>
const emit = defineEmits({
  submit(payload) {
    // возвращает `true` или `false`, чтобы показать
    // что проверка пройдена / не пройдена
  }
})
</script>

Если вы используете TypeScript с <script setup>, можно также объявить генерируемые события с помощью чистых аннотаций типов:

vue
<script setup lang="ts">
const emit = defineEmits<{
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()
</script>

Подробнее: Типизирование пользовательских событий компонента

js
export default {
  emits: {
    submit(payload) {
      // возвращает `true` или `false`, чтобы показать
      // что проверка пройдена / не пройдена
    }
  }
}

См. также: Типизирование пользовательских событий компонента

Хотя это и необязательно, рекомендуется определить все генерируемые события, чтобы лучше документировать, как должен работать компонент. Это также позволяет Vue исключать известных слушателей из проваливающихся атрибутов, избегая пограничных случаев, вызванных событиями DOM, которые вручную отправляются сторонним кодом.

Совет

Если в опции emits определено собственное событие (например, click), слушатель теперь будет прослушивать только события click, генерируемые компонентом, и больше не будет реагировать на собственные события click.

Валидация сгенерированных событий

Аналогично валидации входных параметров, генерируемые события также могут быть валидированы, если это определено с помощью объектного синтаксиса или синтаксиса массива.

Для добавления валидации событию необходимо указать функцию, которая получает аргументы, с которыми вызывался this.$emitemit и возвращает булево, определяющее является ли событие корректным или нет.

vue
<script setup>
const emit = defineEmits({
  // Без валидации
  click: null,

  // Валидация события submit
  submit: ({ email, password }) => {
    if (email && password) {
      return true
    } else {
      console.warn('Некорректные данные для события submit!')
      return false
    }
  }
})

function submitForm(email, password) {
  emit('submit', { email, password })
}
</script>
js
export default {
  emits: {
    // Без валидации
    click: null,

    // Валидация события submit
    submit: ({ email, password }) => {
      if (email && password) {
        return true
      } else {
        console.warn('Некорректные данные для события submit!')
        return false
      }
    }
  },
  methods: {
    submitForm(email, password) {
      this.$emit('submit', { email, password })
    }
  }
}

Использование вместе с v-model

Пользовательские события также можно использовать для создания пользовательских входных данных, которые работают с v-model. Давайте рассмотрим, как v-model используется на нативном элементе:

template
<input v-model="searchText" />

Под капотом компилятор шаблонов расширяет v-model до более точного эквивалента для нас. Таким образом, приведенный выше код делает то же самое, что и следующий:

template
<input
  :value="searchText"
  @input="searchText = $event.target.value"
/>

Вместо этого при использовании на компоненте, v-model расширяется до этого:

template
<CustomInput
  :modelValue="searchText"
  @update:modelValue="newValue => searchText = newValue"
/>

Для того чтобы это действительно работало, компонент <CustomInput> должен выполнять две вещи:

  1. Привязать атрибут value нативного элемента <input> к входному параметру modelValue
  2. При срабатывании нативного события input, сгенерировать пользовательское событие update:modelValue с новым значением

Вот как это выглядит:

vue
<!-- CustomInput.vue -->
<script>
export default {
  props: ['modelValue'],
  emits: ['update:modelValue']
}
</script>

<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>
vue
<!-- CustomInput.vue -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

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

Теперь v-model должна отлично работать с этим компонентом:

template
<CustomInput v-model="searchText" />

Другой способ реализации v-model в этом компоненте - использовать computed свойство с геттером и сеттером. Метод get должен возвращать свойство modelValue, а метод set генерировать соответствующее событие:

vue
<!-- CustomInput.vue -->
<script>
export default {
  props: ['modelValue'],
  emits: ['update:modelValue'],
  computed: {
    value: {
      get() {
        return this.modelValue
      },
      set(value) {
        this.$emit('update:modelValue', value)
      }
    }
  }
}
</script>

<template>
  <input v-model="value" />
</template>
vue
<!-- CustomInput.vue -->
<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 и событие update:modelValue. Их можно изменить с помощью аргумента v-model:

template
<MyComponent v-model:title="bookTitle" />

В таком случае, ожидается что дочерний компонент будет использовать входной параметр title и генерировать событие update:title для синхронизации значения:

vue
<!-- MyComponent.vue -->
<script setup>
defineProps(['title'])
defineEmits(['update:title'])
</script>

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

Попробовать в песочнице

vue
<!-- MyComponent.vue -->
<script>
export default {
  props: ['title'],
  emits: ['update:title']
}
</script>

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

Попробовать в песочнице

Multiple v-model bindings

Развивая потенциал возможности определять конкретный входной параметр и событие, как мы изучили ранее с помощью аргумента v-model, теперь стало возможным создавать несколько привязок v-model на одном экземпляре компонента.

Каждый v-model синхронизирует свой входной параметр, без необходимости дополнительных опций в компоненте:

template
<UserName
  v-model:first-name="first"
  v-model:last-name="last"
/>
vue
<script setup>
defineProps({
  firstName: String,
  lastName: String
})

defineEmits(['update:firstName', 'update:lastName'])
</script>

<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>

Попробовать в песочнице

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

<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. Но в некоторых случаях может потребоваться добавить пользовательские модификаторы.

Создадим для примера пользовательский модификатор capitalize, который станет делать заглавной первую букву строки, привязанной с помощью v-model:

template
<MyComponent v-model.capitalize="myText" />

Модификаторы, которые будут использоваться в v-model компонента, нужно указывать через входной параметр modelModifiers. В примере ниже, у компонента есть входной параметр modelModifiers, который по умолчанию будет пустым объектом.

vue
<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>
vue
<script>
export default {
  props: {
    modelValue: String,
    modelModifiers: {
      default: () => ({})
    }
  },
  emits: ['update:modelValue'],
  created() {
    console.log(this.modelModifiers) // { capitalize: true }
  }
}
</script>

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

Обратите внимание, в хуке жизненного цикла created входной параметр modelModifiers будет содержать capitalize со значением true - потому что он указан на привязке v-model компонента v-model.capitalize="myText".

Теперь, после настройки входного параметра, можно проверять ключи modelModifiers и создать обработчик для изменения значения. Например, будем запускать этот обработчик каждый раз, когда элемент <input /> генерирует событие input.

vue
<script setup>
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)
}
</script>

<template>
  <input type="text" :value="modelValue" @input="emitValue" />
</template>

Попробовать в песочнице

vue
<script>
export default {
  props: {
    modelValue: String,
    modelModifiers: {
      default: () => ({})
    }
  },
  emits: ['update:modelValue'],
  methods: {
    emitValue(e) {
      let value = e.target.value
      if (this.modelModifiers.capitalize) {
        value = value.charAt(0).toUpperCase() + value.slice(1)
      }
      this.$emit('update:modelValue', value)
    }
  }
}
</script>

<template>
  <input type="text" :value="modelValue" @input="emitValue" />
</template>

Попробовать в песочнице

При использовании привязки v-model с аргументом, имя входного параметра будет генерироваться как arg + "Modifiers":

template
<MyComponent v-model:title.capitalize="myText">

Соответствующие объявления должны быть:

js
const props = defineProps(['title', 'titleModifiers'])
defineEmits(['update:title'])

console.log(props.titleModifiers) // { capitalize: true }
js
export default {
  props: ['title', 'titleModifiers'],
  emits: ['update:title'],
  created() {
    console.log(this.titleModifiers) // { capitalize: true }
  }
}
События компонентовУже загружено