Дата последнего изменения: 10.12.2020
Компоненты в рамках Vue - автономные структуры, у которых логика и данные находятся в предсказуемых местах. Зачастую даже при беглом взгляде можно сразу понять, какие используются входные параметры (props
) и какие используются внутренние переменные (data
).
Чтобы компоненты могли взаимодействовать друг с другом, в Vue существует возможность распространять события с помощью .$emit('eventName', eventParams)
и подписываться (слушать) через .$on('eventName', this.eventCallback);
.
Для компонентов Vue не предусмотрен описательный формат для событий, генерируемых компонентом во внешнюю среду (emits
), а также для тех событий, которые компонент слушает из внешней среды (listens
). Однако в рамках системы Bitrix Framework вам следует описывать их в формате комментариев JSDoc в начале компонента.
События в компонентах (emits)
Описание JSDoc для события компонента this.$emit('sendMessage', {text: 'Some text'})
:
/** * @emits 'sendMessage' {text: string} */
Имя события компонента обычно не содержит префикса on
, так как потом оно используется как атрибут при вызове вашего компонента и должно органично смотреться в верстке.
Если используется глобальное событие или событие приложения и о нем должны знать другие разработчики, их также можно описать, поставив метку (global)
или (application)
.
Описание JSDoc для глобального события BX.Vue.event.$emit('onComponentInit', {name: 'ComponentName'})
:
/** * @emits 'onComponentInit' {name: string} (global) */
Описание JSDoc для события приложения this.$root.$emit('onComponentInit', {name: 'ComponentName'})
:
/** * @emits 'onComponentInit' {name: string} (application) */
Отличия глобальных событий, событий приложений и событий компонента
События компонента (локальные события) создаются компонентом, и поймать их может либо текущий компонент, либо родительский компонент, повесив обработчик в виде html-атрибута в шаблоне:
template: ` <div> <bx-component @sendMessage="onSendEventHandle"/> </div> `
По сути, это информирование вышестоящего компонента о выполнении какого-либо события, чтобы он мог исполнить какую-то свою логику.
Глобальные события и события приложения используются для того, чтобы выполнить какое-либо действие в другом компоненте (вызов может быть в разных частях приложения и на разном уровне вложенности).
Между Глобальными событиями и событиями приложения тоже есть разница (забегая вперед - предпочтительнее пользоваться событиями приложения).
В одном экземпляре Vue (в терминах Vue это называется приложение) могут находиться десятки разных компонентов. Чтобы наладить между ними общение, достаточно использовать функцию this.$root.$emit('sendMessage', {text: 'Some text'})
для отправки события и this.$root.$on('sendMessage', this.eventCallback);
для подписки на это событие.
this.$root
означает, что вы отправляете событие в корневой компонент и подписываетесь на получение этого события тоже в корневом компоненте. Обычно этого достаточно, но если на странице используются несколько экземпляров Vue (например, мессенджер и всплывающие уведомления), то в этом случае не получится наладить между ними общение, т.к. они находятся в совершенно разных приложениях.
Для решение этой задачи используется подход Event Bus - Единая шина событий. Она не привязана к конкретному экземпляру Vue и позволяет обмениваться событиями, даже находясь за пределами экземпляров Vue.
Слушатели в компонентах (listeners)
В имени события должен быть префикс вашего модуля, название компонента и название самого действия.
Описание JSDoc для подписки на глобальное событие BX.Vue.event.$on('onModuleNameSomeComponentEvent', this.onInsertText);
:
/** * @listens 'onModuleNameSomeComponentEvent' {} (global) */
Описание JSDoc для подписки на событие приложения this.$root.$on('onModuleNameSomeComponentEvent', this.onInsertText);
:
/** * @listens 'onModuleNameSomeComponentEvent' {} (application) */
Подписку на такие события вам нужно сформировать в методе компонента created
и не забыть отписаться от события в методе beforeDestroy
:
BX.Vue.component('bx-component', { /** * @listens 'onModuleEventName' {text: string} (global) */ created() { BX.Vue.event.$on('onModuleEventName', this.onEventNameHandle); }, beforeDestroy() { BX.Vue.event.$off('onModuleEventName', this.onEventNameHandle); }, ... });
Если разработчик захочет вызвать ваше событие, он должен воспользоваться методом BX.Vue.event.$emit('onModuleNameSomeComponentEvent', {text: 'Some text'})
(для глобально события) и this.$root.$emit('onModuleNameSomeComponentEvent', {text: 'Some text'})
(для события приложения).
Давайте рассмотрим на примере мессенджера:
Синим показан компонент выбора смайлов (smiles
), красным показан компонент ввода и отправки нового сообщения (textarea
).
Оба компонента полностью независимы и не взаимодействуют напрямую друг с другом (они даже не знают о существовании друг друга).
Компонент textarea
подразумевает, что в него могут внешние компоненты публиковать текст, для этого он слушает событие onTextareaInsertText
. На этом его знания об окружающем мире заканчиваются. Это значит, что он может использоваться в совершенно разных контекстах и не привязан к текущей логике.
Компонент smiles
так же может использоваться в разных контекстах, поэтому сам напрямую никому не отправляет данные, а формирует событие onSmileClick
при клике на смайл. При этом совсем не важно, кто будет обрабатывать это событие.
Компоненты объединены общим пространством. Это может быть родительский компонент или, в более сложных случаях, управляющих скрипт или единая шина событий.
В примере, приведенном чуть выше, два компонента объединены общим компонентом. Он подписывается на событие компонента smile
, обрабатывает результат клика и отправляет эти данные через формирование события на обработчик компонента textarea
.
Выглядит это так:
// Компонент кнопок со смайлами BX.Vue.component('bx-smiles', { /** * @emits 'smileClick' {text: string} (global) */ methods: { buttonClick(event) { this.$emit('smileClick', {text: event.target.innerHTML}) } }, template: ` <div> <button @click="buttonClick"> :) </button> <button @click="buttonClick"> :( </button> <button @click="buttonClick"> :D </button> </div> ` }); // Компонент вывода результата BX.Vue.component('bx-textarea', { /** * @listens 'onTextareaInsertText' {text: string} (global) */ data() { return { selectedSmile: '' } }, created() { BX.Vue.event.$on('onTextareaInsertText', this.onInsertText); }, beforeDestroy() { BX.Vue.event.$off('onTextareaInsertText', this.onInsertText); }, methods: { onInsertText(event = {}) { this.selectedSmile = event.text; } }, template: ` <div> <div v-if="selectedSmile">Был выбран смайл: <b>{{selectedSmile}}</b></div> </div> ` }); // Связывающий компонент BX.Vue.component('bx-messenger', { methods: { onSmileClickEvent(event = {}) { BX.Vue.event.$emit('onTextareaInsertText', {text: event.text}) } }, template: ` <div> <bx-smiles @smileClick="onSmileClickEvent"/> <bx-textarea/> </div> ` }); BX.Vue.create({ el: '#vue-application', template: `<bx-messenger/>` });
В данном примере использовались глобальные события, но вполне возможно было использовать и события приложения.
Пример создания универсального компонентаЕсли компонент универсальный и может быть использован в разных местах, то могут возникнуть проблемы: кто-то отправит событие и это событие отработает во всех компонентах данного типа.
Рассмотрим на примере: есть две кнопки, первая публикует один смайл, другая публикует другой смайл - результат нажатия этих двух кнопок публикуется в компонент вывода результата (универсальный).
Как решить озвученную проблему? Нужно сделать название события динамическим и передавать его через входные параметры компонента.
Для таких событий нужно немного по-другому формировать JSDoc @listens
. Вместо названия события необходимо указать, в какой переменной хранится значение, в формате props.varEventName
, где varEventName
это название переменной из объекта props
.
BX.Vue.component('bx-result', { /** * @listens props.resultEventName {text: string} (global) */ props: { resultEventName: { default: '' } // Название переменной намеренно оставлено пустым, чтобы избежать проблемы одинакового именования }, data() { return { result: '' } }, created() { if (this.resultEventName) { BX.Vue.event.$on(this.resultEventName, this.onInsertText); } }, beforeDestroy() { if (this.resultEventName) { BX.Vue.event.$off(this.resultEventName, this.onInsertText); } }, methods: { onInsertText(event = {}) { this.result = event.text; } }, template: ` <div> <div v-if="result">Был выбран смайл: <b>{{result}}</b></div> </div> ` }); BX.Vue.component('bx-button-one', { /** * @emits 'buttonClick' {text: string} (global) */ methods: { buttonClick(event) { this.$emit('buttonClick', {text: event.target.innerHTML}) } }, template: '<button @click="buttonClick"> :) </button>' }); BX.Vue.component('bx-button-two', { /** * @emits 'buttonClick' {text: string} (global) */ methods: { buttonClick(event) { this.$emit('buttonClick', {text: event.target.innerHTML}) } }, template: '<button @click="buttonClick"> :( </button>' }); BX.Vue.component('bx-example', { methods: { buttonClickEvent1(event = {}) { BX.Vue.event.$emit('onResultButton1', {text: event.text}) }, buttonClickEvent2(event = {}) { BX.Vue.event.$emit('onResultButton2', {text: event.text}) }, clear(event = {}) { BX.Vue.event.$emit('onResultButton1', {text: ''}); BX.Vue.event.$emit('onResultButton2', {text: ''}); } }, template: ` <div> <bx-button-one @buttonClick="buttonClickEvent1"/> <bx-button-two @buttonClick="buttonClickEvent2"/> <bx-result resultEventName="onResultButton1"/> <bx-result resultEventName="onResultButton2"/> <button @click="clear">Очистить данные</button> </div> ` }); BX.Vue.create({ el: '#vue-application', template: `<bx-example/>` });