Написание абстрактных компонентов во Vue.js

Alexey Pyltsyn
devSchacht
Published in
3 min readJun 18, 2018

Перевод статьи Joshua Bemenderfer: Writing Abstract Components with Vue.js.

Компоненты Vue великолепны, не так ли? Они инкапсулируют представление и поведение приложения в красивые небольшие компонуемые части. Если вам нужна небольшая дополнительная функциональность, просто присоедините директивы! Дело в том, что директивы довольно негибкие и не подходят для всего. Например, директивы не могут (казалось бы самое простое?) генерировать события. Что ж, это Vue, конечно, есть решение — абстрактные компоненты!

Абстрактные компоненты похожи на обычные компоненты, за исключением того, что они ничего отрисовывают в DOM. Они просто добавляют дополнительное поведение к существующим. Вам наверняка знакомы встроенные абстрактные компоненты Vue, такие как <transition>, <component> и <slot>.

Отличный вариант использования абстрактных компонентов наблюдается, когда элемент входит в область просмотра с помощью IntersectionObserver. Давайте посмотрим на реализацию простого абстрактного компонента для обработки этого.

Если вам нужна соответствующая готовая для продакшена реализация, взгляните на vue-intersect, на котором основана эта статья.

Приступаем к работе

Сначала создадим быстрый компонент, который просто отрисовывает его содержимое. Для этого мы ненадолго посмотрим на функции отрисовки.

Файл IntersectionObserver.vue:

export default {
// Включает абстрактный компонент во Vue.
// Это свойство не задокументировано и может изменить в любой момент,
// но ваш компонент должен работать без него.
abstract: true,
// Ура, функции отрисовки!
render() {
// Без компонента-обёртки, мы можем отрисовать только один дочерний компонент.
try {
return this.$slots.default[0];
} catch (e) {
throw new Error('IntersectionObserver.vue может отрисовывать один и только один дочерний компонент.');
}

return null;
}
}

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

Добавление IntersectionObserver

Хорошо, теперь давайте придерживаться логики для IntersectionObserver.

Внимание: IntersectionObserver изначально не поддерживается IE или Safari, поэтому вам может для него полифил.

Файл IntersectionObserver.vue:

export default {
// Остальной код из предыдущего листинга...

mounted () {
// Нет реальной потребности объявлять наблюдателя в качестве свойства данных,
// потому что он не должен быть реактивным.

this.observer = new IntersectionObserver((entries) => {
this.$emit(entries[0].isIntersecting ? 'intersect-enter' : 'intersect-leave', [entries[0]]);
});

// Нужно подождать следующего тика для того, чтобы дочерний элемент мог отрисоваться.
this.$nextTick(() => {
this.observer.observe(this.$slots.default[0].elm);
});
}
}

Хорошо, теперь у нас есть абстрактный компонент, который можно использовать следующий образом:

<intersection-observer @intersect-enter="handleEnter" @intersect-leave="handleLeave">
<my-honest-to-goodness-component></my-honest-to-goodness-component>
</intersection-observer>

Однако, это ещё не конец.

Заканчиваем

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

export default {
// Остальной код из предыдущих листингов...

destroyed() {
// Кстати, почему W3C выбрало "disconnect" для названия метода?
this.observer.disconnect();
}
}

И в качестве бонусных очков, давайте добавим настраиваемый порог видимости элемента (threshold) для наблюдателя с помощью входных данных.

Файл IntersectionObserver.vue:

export default {
// Остальной код из предыдущих листингов...

// Входные данные работают отлично в абстрактных компонентов!
props: {
threshold: {
type: Array
}
},

// Остальной код из предыдущих листингов...

this.observer = new IntersectionObserver((entries) => {
this.$emit(entries[0].isIntersecting ? 'intersect-enter' : 'intersect-leave', [entries[0]]);
}, {
threshold: this.threshold || 0
});

// Остальной код из предыдущих листингов...
}

Полный код IntersectionObserver.vue можно найти здесь

Окончательное использование будет выглядит так:

<intersection-observer @intersect-enter="handleEnter" @intersect-leave="handleLeave" :threshold="[0, 0.5, 1]">
<my-honest-to-goodness-component></my-honest-to-goodness-component>
</intersection-observer>

Вот так, ваш первый абстрактный компонент!

Большое спасибо Thomas Kjærgaard / Heavyy за первоначальную реализацию и идею!

Слушайте наш подкаст в iTunes и SoundCloud, читайте нас на Medium, контрибьютьте на GitHub, общайтесь в группе Telegram, следите в Twitter и канале Telegram, рекомендуйте в VK и Facebook. Статья на GitHub

--

--