Написание абстрактных компонентов во Vue.js
Перевод статьи 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