Эта статья, прежде всего, для тех, кто только начал работать с Vue и хочет узнать лучше его особенности и возможности. Здесь я хочу рассказать о некоторых возможностях фреймворка, которые часто остаются незаслуженно забытыми начинающими разработчиками.
Render-функции
Шаблоны компонентов — одна из тех вещей, за которые разработчики любят Vue. Они просты и логичны, благодаря им фреймворк имеет низкий порог входа. Синтаксиса шаблонов хватает в 90% случаев, чтобы написать логичный и красивый код. Но, что делать если вы попали в оставшиеся 10%, и написать компактный компонент не получается? Render-функция вам поможет.
Давайте разберемся, что это такое на примере из документации:
Vue.component('anchored-heading', { render: function (createElement) { return createElement( 'h' + this.level, // имя тега this.$slots.default // массив дочерних элементов ) }, props: { level: { type: Number, required: true } } })
Компонент anchored-heading принимает свойство level и отрисовывает тег заголовка. Таким образом, запись
<anchored-heading :level="1">Привет, мир!</anchored-heading>
Будет преобразована в
<h1>Привет, мир!</h1>
Если бы этот компонент был описан с помощью стандартного шаблона, то содержал бы в себе до 6 условий v-if, описывающих разные уровни заголовков:
<h1 v-if="level === 1"> <slot></slot> </h1> <h2 v-if="level === 2"> <slot></slot> </h2> <h3 v-if="level === 3"> <slot></slot> </h3> <h4 v-if="level === 4"> <slot></slot> </h4> <h5 v-if="level === 5"> <slot></slot> </h5> <h6 v-if="level === 6"> <slot></slot> </h6>
Как это работает?
Метод render принимает два аргумента. Первый аргумент createElement — функция описывающая то, какой элемент Vue должен создать. В сообществе принято сокращать createElement до одной буквы — h. Второй аргумент — context, для доступа к контекстным данным.
createElement принимает три аргумента:
- Элемент который нужно создать. Это может быть не только тег HTML, но и имя компонента. Этот аргумент обязательный;
- Объект с данными. Может содержать список классов, стилей, входных параметров для компонента, методы для обработки событий и т.д. Более подробно — в документации. Опциональный аргумент;
- Дочерние виртуальный узлы. Это может быть как строка, так и массив. В примере выше это — this.$slots.default.
Render-функции могут помочь в самых неожиданных ситуациях. Например в Ptah, нам часто требуется использовать тег style внутри страницы для правильной работы некоторых элементов конструктора. Однако Vue запрещает использование этого тега внутри template компонента. Это ограничение с легкостью обходится благодаря небольшой обертке:
Vue.component('v-style', { render: function (h) { return h('style', this.$slots.default) } })
Теперь внутри шаблонов вместо тега style можно использовать v-style.
Итак, как только вам начинает казаться, что стандартных возможностей шаблонов Vue недостаточно — вспомните о Render-функциях. Они выглядят сложными лишь на первый взгляд, но в них можно использовать все возможности которые дает JS.
Mixins
У вас появилось несколько компонентов похожих компонентов с повторяющимся кодом? Mixins или примеси, помогут соблюсти принцип DRY — функционал из миксинов может быть использован в нескольких компонентах сразу.
Разберем на примере. Допустим у нас есть 2 компонента с похожей логикой:
export default { name: 'TextElement', data () { return { elementName: 'Text', showEditor: false, editor: null } }, methods: { initEditor () { this.showEditor = true this.editor = new Editor(this.elementName) } } } export default { name: 'ButtonElement', data () { return { elementName: 'Button', showEditor: false, editor: null } }, methods: { initEditor () { this.showEditor = true this.editor = new Editor(this.elementName) } } }
Компоненты разные, но имеют одинаковую логику. Чтобы вынести ее потребуется создать обычный js файл. Логичным будет разместить его в директории mixins рядом с компонентами.
// mixin.js export default { data () { return { showEditor: false, editor: null } }, methods: { initEditor () { this.showEditor = true this.editor = new Editor(this.elementName) } } } // TextElement.vue import mixin from './mixins/mixin' export default { name: 'TextElement', mixins: [mixin] // используем примесь data () { return { elementName: 'Text', } }, } // ButtonElement.vue import mixin from './mixins/mixin' export default { name: 'ButtonElement', mixins: [mixin] data () { return { elementName: 'Button' } } }
Как видно из примера практически вся логика перекочевала в миксин. При использовании примеси внутри компонентов все их опции сливаются. И в компоненте можно свободно вызвать метод initEditor(), и, наоборот, в примеси здесь используется elementName из компонента. При этом объекты data будут слиты рекурсивно, и свойства из компонента будут иметь приоритет.
Итак, польза примесей очевидна — это повторное использование кода. Но есть и минус. Этот пример синтетический, всего в пару строчек. Реальные компоненты, например те, что используются в Ptah могут быть расписаны на пару сотен строк кода. Человеку, который не писал этот код, не всегда будет ясно как он работает, особенно если он упустит из виду добавление mixins в компонент. К сожалению, полностью избавиться от этого минуса не получится. Порекомендовать я могу две вещи: описывать работу компонента в JSDoc и использовать для свойств из примеси особые имена (например, можно добавлять префикс, о котором вы заранее договоритесь с командой).
Provide / Inject
Эта пара опций всегда используется вместе и позволяет передать данные от компонента родителя во всю иерархию его потомков. Эти опции прежде всего используют для написания плагинов, официальная документация не рекомендует использовать их в приложениях. В приложениях общение между компонентами отлично строится на Vuex. Однако этот функционал все-же заслуживает внимания.
Как это работает?
Для начала нам нужно определить данные в компоненте родителе, которые мы будем передавать его потомкам.
// Parent.vue определяем данные которые хотим передать вниз по иерархии export default { provide: { device: 'is-desktop' } }
Теперь переданные данные нужно внедрить в дочерний компонент.
// Child.vue внедряем данные переданные родителем export default { inject: ['device'], created () { console.log(this.device) // => "is-desktop" } }
Как видно из примера всё довольно просто. Но следует отметить один существенный минус — данные из связки provide/inject по умолчанию не реактивны! Однако этот недостаток легко обойти используя Object.defineProperty:
provide () { let device = {} Object.defineProperty(device, 'type', { enumerable: true, get: () => this.device }) return { device } }, data () { return { device: 'is-desktop' } }
Теперь изменение this.device в родителе изменит его и в потомках.
Мета-компонент Component
Бывают ситуации, когда заранее неизвестно какой компонент будет использован в коде. Рассмотрим пример из нашего редактора. Задача следующая: в условной секции FirstScreen показать элементы Text, Logo, Button, затем к этим элементам добавить SocialIcons.
Итак, очевидно, что у нас будет компонент секции которая будет служить контейнером для элементов и 4 компонента для самих элементов. Структура будет примерно следующая:
/ sections -- FirstScreen.vue / elements -- Text.vue -- Logo.vue -- Button.vue -- SocialIcons.vue
Добавить в шаблон FirstScreen сразу все компоненты элементов, а затем переключать их при помощи условий было бы крайне неразумным решением. Для подобных задач есть простой и замечательный инструмент:
<component :is="%componentName%"/>
Элемент component с атрибутом :is в который просто записывается имя компонента. И наша задача благодаря нему решается просто элементарно:
<script> export default { name: 'FirstScreen', data () { return { elements: [ 'Text', 'Logo', 'Button', ], } } } </script> <template> <div class="first-screen"> <component v-for="element in elements" :is="element"/> </div> </template>
В массив elements мы записали имена компонентов и затем просто выводим эти компоненты в цикле внутри шаблона FirstScreen. Теперь для того чтобы добавить в нашу секцию элемент с иконками соцсетей, нам нужно всего лишь выполнить this.elements.push(‘SocialIcons’).
ссылка на оригинал статьи https://habr.com/ru/post/491130/
Добавить комментарий