JavaScript

Реализация шаблона Builder во Vue.js, часть 1: Списки

Spread the love

Перевод: Markus Oberlehner — Implementing the Builder Pattern in Vue.js Part 1: Listings

Недавно я увидел отличный доклад Джейкоба Шаца Phenomenal Design Patterns in Vue. Одним из паттернов, которые он упомянул в своем выступлении, был паттерн Строителя (Builder). Я нашел его пример очень интересным, поэтому мне было ясно, что я должен сам поэкспериментировать с этим шаблоном.

В этой статье мы рассмотрим возможную реализацию шаблона Builder, специально адаптированного к тому, как я обычно структурирую свои приложения Vue.js. В первой части этой статьи состоящей из двух частей я использую этот шаблон для создания многоразовых представлений списков (представьте себе приложение с множеством списочных представлений для множества различных типов контента), а во второй части я рассмотрю возможное решение для создания множества форм с использованием этого подхода.

Шаблон 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.

https://codesandbox.io/embed/implementing-the-builder-pattern-in-vuejs-listings-y2p0m8v5zz?fontsize=14&module=%2Fsrc%2FApp.vue&view=editor


Улучшение возможности повторного использования с помощью шаблона Director

Хотя мы увидели, что мы можем уменьшить большую часть работы на начальном этапе, позволив классу 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 для создания переиспользуемых форм.

Была ли вам полезна эта статья?
[4 / 2.8]

Spread the love
Editorial Team

Recent Posts

Vue 3.4 Новая механика v-model компонента

Краткий перевод: https://vuejs.org/guide/components/v-model.html Основное использование​ v-model используется для реализации двусторонней привязки в компоненте. Начиная с Vue…

12 месяцев ago

Анонс Vue 3.4

Сегодня мы рады объявить о выпуске Vue 3.4 «🏀 Slam Dunk»! Этот выпуск включает в…

12 месяцев ago

Как принудительно пере-отобразить (re-render) компонент Vue

Vue.js — это универсальный и адаптируемый фреймворк. Благодаря своей отличительной архитектуре и системе реактивности Vue…

2 года ago

Проблемы с установкой сертификата на nginix

Недавно, у меня истек сертификат и пришлось заказывать новый и затем устанавливать на хостинг с…

2 года ago

Введение в JavaScript Temporal API

Каким бы ни было ваше мнение о JavaScript, но всем известно, что работа с датами…

2 года ago

Когда и как выбирать между медиа запросами и контейнерными запросами

Все, кто следит за последними событиями в мире адаптивного дизайна, согласятся, что введение контейнерных запросов…

2 года ago