359  /  396

События и слушатели в компонентах

Просмотров: 2359 (Статистика ведётся с 06.02.2017)
Анна Кокина
Сложность урока:
4 уровень - сложно, требуется сосредоточится, внимание деталям и точному следованию инструкции.
1
2
3
4
5
Недоступно в редакциях:
Ограничений нет

События и слушатели в компонентах

Компоненты в рамках 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/>`
});


1
Курсы разработаны в компании «1С-Битрикс»

Если вы нашли неточность в тексте, непонятное объяснение, пожалуйста, сообщите нам об этом в комментариях.
Развернуть комментарии