Презентационные и контейнерные компоненты

FAST Element — это современная библиотека для создания веб-компонентов с использованием стандарта Custom Elements и Shadow DOM. Она обеспечивает высокую производительность, декларативный синтаксис и мощный механизм реактивности, что делает её удобной как для создания маленьких UI-компонентов, так и для сложных модульных приложений. Важное различие в архитектуре FAST Element заключается в разделении презентационных и контейнерных компонентов, что напрямую влияет на поддержку повторного использования кода, тестируемость и управление состоянием.


Презентационные компоненты

Презентационные компоненты (presentational components) отвечают исключительно за отображение данных и визуальное представление. Они не содержат бизнес-логики и получают все необходимые данные через свойства или слоты. Основная цель таких компонентов — быть максимально универсальными и переиспользуемыми.

Особенности

  • Декларативное отображение: FAST Element использует HTML-шаблоны и директивы для реактивного связывания данных.
  • Реактивные свойства: Объявляются с помощью @attr или @observable, что позволяет автоматически обновлять DOM при изменении данных.
  • Слоты: Обеспечивают гибкость вложения произвольного контента, что делает компонент более универсальным.
  • Отсутствие внутреннего состояния: Любые данные приходят через входные свойства.

Пример

import { FASTElement, html, css, customElement, attr } from "@microsoft/fast-element";

const template = html<MyButton>`
  <button class="my-button">${x => x.label}</button>
`;

const styles = css`
  .my-button {
    padding: 8px 16px;
    border: none;
    background-color: #0078d4;
    color: white;
    cursor: pointer;
    font-size: 14px;
  }
`;

@customElement({ name: "my-button", template, styles })
export class MyButton extends FASTElement {
  @attr label = "";
}

В этом примере компонент MyButton не содержит логики обработки кликов или состояния, он лишь отображает текст, переданный через атрибут label.


Контейнерные компоненты

Контейнерные компоненты (container components) концентрируются на управлении состоянием, логике взаимодействия между компонентами и обработке данных. Они часто используют презентационные компоненты для отображения, создавая таким образом разделение ответственности.

Особенности

  • Управление состоянием: Контейнерные компоненты могут хранить локальное состояние или подключаться к глобальному стору.
  • Передача данных презентационным компонентам: Контейнеры не рисуют UI напрямую, они используют композицию.
  • Обработка событий: Подписываются на события дочерних компонентов, чтобы изменять состояние или инициировать действия.
  • Логика асинхронных операций: Например, получение данных с API, фильтрация или сортировка перед отображением.

Пример

import { FASTElement, html, customElement, observable } from "@microsoft/fast-element";
import "./my-button.js";

const template = html<MyApp>`
  <div>
    <my-button label="${x => x.buttonLabel}" @click="${x => x.increment()}"></my-button>
    <p>Кликнули: ${x => x.count} раз</p>
  </div>
`;

@customElement({ name: "my-app", template })
export class MyApp extends FASTElement {
  @observable count = 0;

  get buttonLabel() {
    return `Нажми меня`;
  }

  increment() {
    this.count++;
  }
}

Здесь MyApp является контейнерным компонентом: он управляет состоянием count и передает данные в презентационный компонент MyButton. Логика обработки кликов полностью отделена от визуального отображения.


Комбинация презентационных и контейнерных компонентов

Сочетание этих двух типов компонентов позволяет строить архитектуру с высокой модульностью и тестируемостью:

  • Презентационные компоненты остаются чистыми и легко тестируемыми.
  • Контейнерные компоненты обрабатывают состояние и бизнес-логику, но не смешивают её с UI.
  • Любые изменения визуального представления не затрагивают логику и наоборот.

Пример композиции

import "./my-button.js";
import { FASTElement, html, customElement, observable } from "@microsoft/fast-element";

const template = html<MyDashboard>`
  <div>
    <my-button label="Добавить элемент" @click="${x => x.addItem()}"></my-button>
    <ul>
      ${x => x.items.map(item => html`<li>${item}</li>`)}
    </ul>
  </div>
`;

@customElement({ name: "my-dashboard", template })
export class MyDashboard extends FASTElement {
  @observable items = [];

  addItem() {
    this.items = [...this.items, `Элемент ${this.items.length + 1}`];
  }
}

В этом случае контейнерный компонент управляет списком items, а презентационный компонент отвечает за кнопку. Такой подход упрощает поддержку сложных интерфейсов и повышает переиспользуемость кода.


Рекомендации по проектированию

  1. Разделение ответственности: Логика состояния и UI всегда разделяется.
  2. Минимизация зависимости: Презентационные компоненты не должны знать о глобальном состоянии или API.
  3. Повторное использование: Любой презентационный компонент должен быть максимально универсальным.
  4. Композиция вместо наследования: Использование контейнеров для объединения нескольких презентационных компонентов улучшает масштабируемость приложения.
  5. Реактивность: Использование @observable и привязок данных в шаблоне позволяет автоматически обновлять UI без ручного манипулирования DOM.

Итоговые преимущества FAST Element при разделении компонентов

  • Высокая производительность: Реактивные свойства обновляют только изменившиеся части DOM.
  • Чистая архитектура: Легко поддерживать и тестировать отдельные компоненты.
  • Гибкость и расширяемость: Новые функциональные возможности добавляются через контейнерные компоненты, не затрагивая визуальные элементы.
  • Совместимость с современными стандартами: Полная поддержка Shadow DOM и Custom Elements.

Такое разделение компонентов формирует основу устойчивого и масштабируемого фронтенд-приложения на базе FAST Element.