Пользовательские события

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

Стиль именования событий

В отличие от компонентов и входных параметров, имена событий не предоставляют никакой автоматической трансформации стиля именования события. Вместо этого, имя генерируемого события должно точно соответствовать имени, используемому при прослушивании события. К примеру, если генерируем событие с именем в camelCase:

this.$emit('myEvent')

Прослушивание kebab-cased версии этого имени не будет иметь никакого эффекта:

<!-- НЕ БУДЕТ РАБОТАТЬ -->
<my-component v-on:my-event="doSomething"></my-component>

В отличие от компонентов и входных параметров, имена событий никогда не будут использоваться в качестве имён переменных или имён свойств в JavaScript, поэтому нет причин использовать camelCase или PascalCase. Кроме того, директивы прослушивания событий v-on внутри DOM-шаблонов автоматически преобразуются в нижний регистр (из-за нечувствительности HTML к регистру), поэтому v-on:myEvent станет v-on:myevent — что делает прослушивание события myEvent невозможным.

По этим причинам мы рекомендуем всегда использовать kebab-case для имён событий.

Настройка v-model у компонента

Добавлено в версии 2.2.0+

По умолчанию v-model на компоненте использует входной параметр value и событие input. Но некоторые типы полей, такие как чекбоксы или радиокнопки, могут использовать атрибут value для других целей. Использование опции model позволит избежать конфликта в таких случаях:

Vue.component('base-checkbox', {
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: {
    checked: Boolean
  },
  template: `
    <input
      type="checkbox"
      v-bind:checked="checked"
      v-on:change="$emit('change', $event.target.checked)"
    >
  `
})

Теперь, когда используем v-model на этом компоненте:

<base-checkbox v-model="lovingVue"></base-checkbox>

значение lovingVue будет передано во входном параметре checked. А обновляться свойство lovingVue будет когда <base-checkbox> сгенерирует событие change с новым значением.

Обратите внимание, что вам всё равно нужно объявлять входной параметр checked в опции props компонента.

Подписка на нативные события в компонентах

Иногда нужно подписаться на нативные события браузера на корневом элементе компонента. В таких случаях можно применять модификатор .native для v-on:

<base-input v-on:focus.native="onFocus"></base-input>

Иногда это может быть полезно, но это не очень хорошая идея, когда вы пытаетесь прослушивать очень специфичный элемент, к примеру <input>. Например, компонент <base-input> можно переделать так, чтобы корневой элемент был фактически элементом <label>:

<label>
  {{ label }}
  <input
    v-bind="$attrs"
    v-bind:value="value"
    v-on:input="$emit('input', $event.target.value)"
  >
</label>

В этом случае слушатель .native в родителе просто тихо перестанет работать. Ошибок не будет, но обработчик onFocus не будет вызываться, когда мы этого ожидаем.

Для решения этой проблемы Vue предоставляет свойство $listeners, содержащее объект всех слушателей, которые используются на компоненте. Например:

{
  focus: function (event) { /* ... */ }
  input: function (value) { /* ... */ },
}

Используя свойство $listeners, вы можете передать все слушатели событий на компоненте на определённый дочерний элемент с помощью v-on="$listeners". Для таких элементов как <input>, вы также можете захотеть работоспособности с v-model, для чего бывает полезно создать новое вычисляемое свойство для слушателей, например inputListeners указанный ниже:

Vue.component('base-input', {
  inheritAttrs: false,
  props: ['label', 'value'],
  computed: {
    inputListeners: function () {
      var vm = this
      // `Object.assign` объединяет объекты вместе, чтобы получить новый объект
      return Object.assign({},
        // Мы добавляем все слушатели из родителя
        this.$listeners,
        // Затем мы можем добавить собственные слушатели или
        // перезаписать поведение некоторых существующих.
        {
          // Это обеспечит, что будет работать v-model на компоненте
          input: function (event) {
            vm.$emit('input', event.target.value)
          }
        }
      )
    }
  },
  template: `
    <label>
      {{ label }}
      <input
        v-bind="$attrs"
        v-bind:value="value"
        v-on="inputListeners"
      >
    </label>
  `
})

Теперь компонент <base-input> стал полностью прозрачной обёрткой, что означает, что его можно использовать точно также, как и обычный элемент <input>: все те же атрибуты и слушатели будут работать без указания модификатора .native.

Модификатор .sync

Добавлено в версии 2.3.0+

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

Поэтому вместо этого, мы рекомендуем генерировать события с определённым шаблоном имени update:myPropName. Например, в гипотетическом компоненте с входным параметром title, мы можем сообщить о намерении присвоить новое значение:

this.$emit('update:title', newTitle)

Затем, родитель может прослушать это событие и обновить локальное свойство, если захочет. Например:

<text-document
  v-bind:title="doc.title"
  v-on:update:title="doc.title = $event"
></text-document>

Для удобства мы предлагаем краткую запись для этого шаблона с помощью модификатора .sync:

<text-document v-bind:title.sync="doc.title"></text-document>

Обратите внимание, что v-bind с модификатором .sync не работает с выражениями (например, v-bind:title.sync=”doc.title + ‘!’” не будет работать). Вместо этого нужно указывать только имя свойства, которое хотите привязать, аналогично v-model.

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

<text-document v-bind.sync="doc"></text-document>

Это передаёт каждое свойство в объекте doc (например, title) в качестве индивидуальных входных параметров, а затем добавляет слушатели событий v-on для каждого из них.

Использование v-bind.sync с литеральным объектом, например таким как v-bind.sync=”{ title: doc.title }”, не будет работать, потому что необходимо будет учитывать слишком много различных пограничных случаев при анализе подобного сложного выражения.