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

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

Last updated