Компоненты без рендеринга во Vue
Перевод: Marina Mosti — Understanding Renderless Components in Vue
Компоненты без рендеринга (Renderless Components) — это компоненты, которые сами по себе не выводят HTML-код в DOM. Они довольно редко используются, так как в основном служат в качестве логических оболочек, которые вы можете использовать в своих шаблонах и передавать в них настраиваемый фрагмент HTML. Они как правило содержат свою исполняемую логику и внедрять ее в ваш HTML через так называемую область видимости scope.
В этой статье я расскажу, о том как создавать компоненты без рендеринга во Vue, а также как и когда их использовать. Имейте в виду, что из-за ограничения объема статьи предполагается, что у вас есть базовые знания о слотах (slot) Vue и слотах с ограниченной областью действия (scoped slot).
Если вам нужно освежить в памяти слоты с ограниченной областью видимости (scoped slots), вы можете ознакомиться с документацией по слотам с ограниченной областью действия или взглянуть на эти статьи в блоге Vue slots и Vue scoped slots.
Когда используются компоненты без рендеринга?
Если вы когда-нибудь создавали компонент, внутри которого есть определенная логика, но хотите, чтобы пользователь этого компонента мог написать для него любой собственный HTML-код и использовать эту логику, тогда компоненты без рендеринга могут быть одним из ваших решений.
Обратите внимание, что это может быть одним из ваших решений, потому что этого также можно достичь, написав миксин или даже стандартный класс или функцию JS, которые внедряют это поведение в ваши компоненты. Фактически, можно сказать, что в большинстве случаев функциональный подход будет лучше в любом виде и в любой форме — если думать в терминах Vue 3 и API Composition, переиспользуемого и инкапсулированного кода, который можно внедрить и использовать в любом из ваших компонентов.
Что касается миксинов, в частности, помните, что у них есть недостаток в виде потенциальный коллизий с другими частями вашего компонента. По сравнению с этим недостатком компоненты без рендеринга действительно выглядят более привликательными благодаря инкапсуляции логики.
Теперь, сказав это, рассмотрим сценарии, в которых вы можете решить какую либо проблему с компонентом без рендеринга просто потому, что вы хотите реализовать какое то поведение в DOM, или потому что вы пишете библиотеку компонентов, которая должна быть сверхгибкой.
В этой статье мы рассмотрим базовый пример, демонстрирующий сценарий реализации гибкого компонента. Представьте, что вы пишете компонент, который может сортировать массив объектов по определенному свойству, но вы не хотите быть строгими в отношении того, как это содержимое должно отображаться.
Возможно, пользователь захочет поместить данные компонента в <ol> или даже использовать <table>. В этом случае хорошим решением может быть компонент без рендеринга.
Создание компонента без рендеринга
Чтобы начать работу с компонентом без рендеринга, давайте посмотрим на самую базовую реализацию, которую можно было бы сделать, и на то, как мы можем использовать ее на другом компоненте.
Компонент без рендеринга не имеет <template> потому что он ничего не выводит в DOM. Однако у него есть функция рендеринга render, которая предоставляет один слот с ограниченной областью видимости. Таким образом, все, что мы разместим в нем, будет отображаться в шаблоне родителя — что является нормальным поведением слота.
Прежде всего, давайте создадим наш компонент OrderedObjects.
# OrderedObjects.vue <script> export default { render() { return this.$scopedSlots.default({}); } }; </script>
Как я упоминал ранее, единственное реальное требование здесь — это вернуть один scopedSlot по умолчанию. {} в функции — это то место, где мы собираемся предоставить данные родителю позже, но пока не беспокойтесь об этом.
Вернемся к App.vue или к тому месту, где вы размещаете свои фактические компоненты, и воспользуемся этим компонентом. Не забудьте импортировать его и добавить в компоненты: но сначала {}!
# App.vue <template> <div id="app"> <OrderedObjects>Hi!</OrderedObjects> </div> </template> <script> import OrderedObjects from "./components/OrderedObjects"; export default { components: { OrderedObjects } }; </script>
Если вы запустите этот код в своем браузере прямо сейчас, вы увидите только Hi! строка выводится в строку, что означает, что scopedSlot выполняет свою работу!
Затем давайте создадим фиктивные данные для экспериментов и передадим их в OrderedObjects. Сначала мы создадим данные в App.vue.
# App.vue <template> <div id="app"> <OrderedObjects :objects="stuffs">Hi!</OrderedObjects> </div> </template> <script> import OrderedObjects from "./components/OrderedObjects"; export default { components: { OrderedObjects }, data() { return { stuffs: [ { name: "some", importance: 2 }, { name: "stuffs", importance: 1 }, { name: "and", importance: 1 }, { name: "things", importance: 0 }, { name: "Goku", importance: 9001 } ] }; } }; </script>
Во-первых, мы добавили данные в объект data () нашего родителя и поместили в него фиктивные данные. Убедитесь, что вы добавили :objects = «stuffs» к фактическому элементу OrderedObjects в шаблоне. Мы собираемся создать внутри него props objects.
# OrderedObjects.vue <script> export default { props: { objects: { type: Array, required: true } }, render() { return this.$scopedSlots.default({}); } }; </script>
Теперь, когда мы добавили props objects в наш компонент OrderedObjects, мы действительно можем начать ее использовать. Предполагается, что этот компонент будет упорядочивать объекты за нас, но пока давайте просто вернем список родительскому элементу в том виде, в каком он был нам предоставлен.
Добавьте свойство objects к объекту scopedSlot, как показано ниже.
# OrderedObjects.vue <script> export default { props: { objects: { type: Array, required: true } }, render() { return this.$scopedSlots.default({ objects: this.objects }); } }; </script>
Если вы проверите свой браузер прямо сейчас, пока ничего не изменится. Это потому, что мы еще не использовали родительские данные. Вернемся к App.vue и внесем следующие изменения.
# App.vue <template> <div id="app"> <OrderedObjects :objects="stuffs"> <template v-slot:default="{objects}"> <ul> <li v-for="obj in objects" :key="obj.name"> {{ obj.importance }} - {{ obj.name }} </li> </ul> </template> </OrderedObjects> </div> </template>
Если вы вернетесь в свой браузер, вы увидите, что теперь у нас есть список элементов, отображаемых на экране. Помните объект, который мы передали вместе со свойством objects в нашей функции рендеринга в прошлой части?
{ objects: this.objects }
Это именно то, что мы получаем из слота с ограниченной областью видимости в этой строке. Затем мы используем деструктуризацию JavaScript, чтобы распаковать его.
<template v-slot:default="{objects}">
Прямо сейчас мы мало что делаем внутри OrderedObjects с нашими данными, мы просто передаем их обратно. Итак, давайте изменим наш компонент, чтобы фактически изменять сортировку наших данных по имени.
# OrderedObjects.vue <script> export default { props: { objects: { type: Array, required: true } }, render() { return this.$scopedSlots.default({ objects: this.orderedObjs }); }, computed: { orderedObjs() { const objs = [...this.objects]; return objs.sort((a, b) => { if (a.name.toLowerCase() > b.name.toLowerCase()) return 1; if (a.name.toLowerCase() < b.name.toLowerCase()) return -1; return 0; }); } } }; </script>
Сначала мы создали вычисляемое свойство с именем orderedObjs. Внутри этого вычисляемого свойства мы делаем копию массива this.objects (если вы пропустите этот шаг, вы измените prop, а это очень плохо!).
Затем мы применяем функцию сортировки к копии массива, которая просто оценивает свойство name и упорядочивает элементы.
Наконец, мы используем это новое вычисляемое свойство в нашей функции рендеринга. Вместо того, чтобы передавать в слот с заданной областью действия свойство this.objects, мы передаем обратно this.orderedObjs.
Проверьте свой браузер сейчас, и вы увидите, что данные в списке теперь упорядочены по имени!
Теперь, когда вы знаете, как создать компонент без рендеринга и как он работает, давайте создадим второй способ визуализации этого списка, чтобы лучше продемонстрировать реальную полезность этих компонентов.
Вернитесь в App.vue и добавьте следующий код:
# App.vue <template> <div id="app"> <OrderedObjects :objects="stuffs"> <template v-slot:default="{objects}"> <ul> <li v-for="obj in objects" :key="obj.name">{{ obj.importance }} - {{ obj.name }}</li> </ul> </template> </OrderedObjects> <OrderedObjects :objects="stuffs"> <template v-slot:default="{objects}"> <table> <tr v-for="obj in objects" :key="obj.name"> <td>{{ obj.importance }}</td> <td>{{ obj.name }}</td> </tr> </table> </template> </OrderedObjects> </div> </template>
Обратите внимание на второе использование OrderedObjects. Мы загружаем в него те же самые данные, массив stuffs в свойство objects. Однако на этот раз мы фактически собираемся отобразить наши данные в таблице.
Благодаря мощности слотов с ограниченной областью видимости и обработке данных, которая инкапсулирует наш компонент без рендеринга, теперь у нас может быть компонент-оболочка, которая изменяет, анализирует или даже обращается к API, примеру для анализа данные. И все это с гибкостью, позволяющей пользователям этого компонента передавать свой собственный HTML-код для отображения результатов так, как они считают нужным!
Код для этой статьи можно найти в следующей песочнице: https://codesandbox.io/s/renderless-components-prqmt
Заключение
Компоненты без рендеринга — это лишь один из способов добиться инкапсуляции совместно используемого или многократно используемого кода. Они решают конкретную проблему, связанную с желанием иметь эту возможность совместного использования непосредственно в вашем шаблоне, но также могут быть заменены с помощью решений, обсуждаемых в начале этой главы.
Тем не менее, это отличный инструмент, который нужно знать (и понимать!) Во множестве других инструментов Vue!
Как всегда, спасибо за чтение и поделитесь со мной своим опытом работы с компонентами без рендеринга в Twitter по адресу: @marinamosti.
Прим. переводчика: далее идет не переводимая игра слов и смайликов….
P.S. All hail the magical avocado! 🥑
P.P.S. ❤️🔥🐶☠️