Перевод: Markus Oberlehner — Implementing the Builder Pattern in Vue.js Part 1: Listings
Недавно я увидел отличный доклад Джейкоба Шаца Phenomenal Design Patterns in Vue. Одним из паттернов, которые он упомянул в своем выступлении, был паттерн Строителя (Builder). Я нашел его пример очень интересным, поэтому мне было ясно, что я должен сам поэкспериментировать с этим шаблоном.
В этой статье мы рассмотрим возможную реализацию шаблона Builder, специально адаптированного к тому, как я обычно структурирую свои приложения Vue.js. В первой части этой статьи состоящей из двух частей я использую этот шаблон для создания многоразовых представлений списков (представьте себе приложение с множеством списочных представлений для множества различных типов контента), а во второй части я рассмотрю возможное решение для создания множества форм с использованием этого подхода.
Шаблон строителя (Builder) — это шаблон создания (Creational) в объектно-ориентированном программировании. «Creational» означает, что он обычно используется для упрощения процесса создания новых объектов. В приложениях Vue.js все строится на основе компонентов, и в нашем случае мы хотим, чтобы класс строителя создавал для нас новый компонент (так как на самом деле компонент не что иное, как объект).
Прежде чем мы посмотрим на код, давайте начнем с определения проблемы, которую мы хотим решить. Подумайте о следующем сценарии: мы должны создать приложение, которое будет использоваться для управления множеством различных типов контента (например, пользователями, продуктами, сообщениями, статьями, тегами и т. д.). Для каждого из этих типов контента нам нужен список. Некоторые типы контента лучше подходят для табличных представлений, а другие должны отображаться в виде сетки. Опять же, нужна система фильтров, а большинству также нужна навигация по страницам.
В следующем фрагменте кода вы можете увидеть возможное решение для класса ListingBuilder, которое можно использовать для создания всех этих различных вариантов представлений списков для нас.
// src/builders/ListingBuilder.js import EntityListing from '../components/EntityListing.vue'; export default class ListingBuilder { constructor() { this.props = {}; } withProvider(provider) { this.provider = provider; return this; } withListingItem(item) { this.item = item; return this; } showFilter() { this.props.showFilter = true; return this; } showPagination() { this.props.showPagination = true; return this; } view(view) { this.props.view = view; return this; } build() { const Provider = this.provider; const Item = this.item; const props = this.props; return { render(h) { return h(Provider, [ h( EntityListing, { props, scopedSlots: { default: props => h(Item, { props }) }, }, [Item], ), ]); }, }; }, };
Как вы можете видеть выше, в нашем ListingBuilder есть несколько методов, которые мы можем использовать для управления тем, какой вид листингового представления будет создан для нас, как только мы вызовем метод build ().
<template> <div id="app"> <h2>Product Listing</h2> <ProductListing/> <h2>User Listing</h2> <UserListing/> </div> </template> <script> // src/App.vue import ListingBuilder from './builders/ListingBuilder'; import ProductListingItem from './components/ProductListingItem.vue'; import ProductProvider from './components/ProductProvider.vue'; import UserListingItem from './components/UserListingItem.vue'; import UserProvider from './components/UserProvider.vue'; export default { name: 'App', components: { ProductListing: new ListingBuilder() .withProvider(ProductProvider) .withListingItem(ProductListingItem) .showFilter() .showPagination() .view('grid') .build(), UserListing: new ListingBuilder() .withProvider(UserProvider) .withListingItem(UserListingItem) .showPagination() .view('table') .build(), }, }; </script>
В этом примере вы можете увидеть, как мы можем использовать класс ListingBuilder для быстрого создания двух разных списочных компонентов. Компонент ProductListing имеет собственные реализации ProductProvider и ProductListingItem, имеет фильтры и разбивку на страницы. Кроме того, он использует сетку общего компонента EntityListing. Компонент UserListing, с другой стороны, использует немного другую конфигурацию.
В следующем фрагменте кода вы можете увидеть, как мы могли бы достичь того же результата, используя вместо этого отдельный компонент ProductListing.
<template> <ProductProvider> <EntityListing view="grid" show-filter show-pagination v-slot="{ entity }" > <ProductListingItem :entity="entity"/> </EntityListing> </ProductProvider> </template> <script> // src/components/ProductListing.vue import EntityListing from './EntityListing.vue'; import ProductListingItem from './ProductListingItem.vue'; import ProductProvider from './ProductProvider.vue'; export default { name: 'ProductListing', components: { EntityListing, ProductListingItem, ProductProvider, }, }; </script>
Результат кода, который вы видите выше, точно такой же, как и при использовании нашего класса ListingBuilder для создания компонента ProductListing. Если вам интересен остальной код, вы можете взглянуть на следующий CodeSandbox.
Хотя мы увидели, что мы можем уменьшить большую часть работы на начальном этапе, позволив классу ListingBuilder выполнять тяжелую работу, этот шаблон может привести к увеличению объема кода в конце (в момент использования), если нам придется повторно использовать компонент ProductListing в нескольких местах. С отдельным компонентом мы можем просто импортировать и повторно использовать компонент, но когда мы используем ListingBuilder, мы должны повторять логику инициализации снова и снова везде, где нам нужен компонент.
Решение этой проблемы: шаблон Director.
// src/builders/ListingDirector.js import ProductListingItem from '../components/ProductListingItem.vue'; import ProductProvider from '../components/ProductProvider.vue'; import UserListingItem from '../components/UserListingItem.vue'; import UserProvider from '../components/UserProvider.vue'; export default class ListingDirector { constructor(builder) { this.builder = builder; } makeProductListing() { return this.builder .withProvider(ProductProvider) .withListingItem(ProductListingItem) .showFilter() .showPagination() .view('grid') .build(); } makeUserListing() { return this.builder .withProvider(UserProvider) .withListingItem(UserListingItem) .showPagination() .view('table') .build(); } }
<script> // src/App.vue import ListingBuilder from './builders/ListingBuilder'; import ListingDirector from './builders/ListingDirector'; export default { name: 'App', components: { ProductListing: new ListingDirector( new ListingBuilder() ).makeProductListing(), UserListing: new ListingDirector( new ListingBuilder() ).makeUserListing(), }, }; </script>
Выше вы можете увидеть, как мы можем использовать ListingDirector для повторного использования существующих конфигураций компонентов списка.
Хотя я определенно вижу сильные стороны этого шаблона, он ощущается как инородное тело в типичном приложении Vue.js. Я не думаю, что это тот шаблон, который вы будете часто использовать в каждом приложении, которое вы создаете, а скорее шаблон, который вы используете только в очень определенных ситуациях.
Но это была только первая часть этой серии из двух частей. Прочитайте вторую часть этой серии, чтобы узнать больше о том, как использовать шаблон Builder для создания переиспользуемых форм.
Краткий перевод: https://vuejs.org/guide/components/v-model.html Основное использование v-model используется для реализации двусторонней привязки в компоненте. Начиная с Vue…
Сегодня мы рады объявить о выпуске Vue 3.4 «🏀 Slam Dunk»! Этот выпуск включает в…
Vue.js — это универсальный и адаптируемый фреймворк. Благодаря своей отличительной архитектуре и системе реактивности Vue…
Недавно, у меня истек сертификат и пришлось заказывать новый и затем устанавливать на хостинг с…
Каким бы ни было ваше мнение о JavaScript, но всем известно, что работа с датами…
Все, кто следит за последними событиями в мире адаптивного дизайна, согласятся, что введение контейнерных запросов…