Паттерн контроллеров

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

Контроллеры представляют собой объекты, которые управляют поведением компонента, реагируют на изменения свойств, событий и жизненного цикла, не вмешиваясь напрямую в DOM-структуру компонента. Основное преимущество такого подхода — разделение ответственности: компонент отвечает за отображение, контроллер — за логику.


Создание контроллера

Контроллер должен реализовывать интерфейс FASTElementController или использовать типовую структуру с методами жизненного цикла:

class ExampleController {
    constructor(host) {
        this.host = host; // ссылка на компонент
    }

    connectedCallback() {
        console.log('Контроллер подключен к компоненту');
    }

    disconnectedCallback() {
        console.log('Контроллер отсоединен от компонента');
    }

    hostUpdated() {
        console.log('Свойства компонента изменились');
    }
}
  • constructor(host) — принимает ссылку на компонент, к которому привязан контроллер.
  • connectedCallback() — вызывается, когда компонент подключен к DOM.
  • disconnectedCallback() — вызывается при удалении компонента из DOM.
  • hostUpdated() — вызывается после обновления реактивных свойств компонента.

Подключение контроллера к компоненту

FAST Element предоставляет метод addController для регистрации контроллера внутри компонента:

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

class MyComponent extends FASTElement {
    constructor() {
        super();
        this.addController(new ExampleController(this));
    }
    
    static template = html`<div>Пример компонента</div>`;
}

Вызов addController обеспечивает автоматический вызов методов жизненного цикла контроллера: connectedCallback, disconnectedCallback, hostUpdated.


Контроллеры и реактивные свойства

Контроллер может реагировать на изменения свойств компонента, используя метод hostUpdated(). Для фильтрации изменений конкретных свойств можно хранить предыдущие значения:

class PropertyWatcherController {
    constructor(host) {
        this.host = host;
        this.prevValue = host.value;
    }

    hostUpdated() {
        if (this.host.value !== this.prevValue) {
            console.log(`Свойство изменилось с ${this.prevValue} на ${this.host.value}`);
            this.prevValue = this.host.value;
        }
    }
}

Такой подход позволяет создавать контроллеры для валидации, форматирования данных, управления состояниями UI без загрязнения основного кода компонента.


Контроллеры и события

Контроллеры могут подписываться на события компонента или DOM-элементов внутри него. В connectedCallback удобно добавлять слушатели:

class EventController {
    constructor(host) {
        this.host = host;
        this.handleClick = this.handleClick.bind(this);
    }

    connectedCallback() {
        this.host.addEventListener('click', this.handleClick);
    }

    disconnectedCallback() {
        this.host.removeEventListener('click', this.handleClick);
    }

    handleClick(event) {
        console.log('Клик по компоненту', event);
    }
}

Методы connectedCallback и disconnectedCallback обеспечивают корректное подключение и очистку обработчиков, предотвращая утечки памяти.


Множественные контроллеры

Один компонент может использовать несколько контроллеров одновременно. Порядок вызова методов жизненного цикла контроллеров соответствует порядку их добавления:

class ComponentWithControllers extends FASTElement {
    constructor() {
        super();
        this.addController(new EventController(this));
        this.addController(new PropertyWatcherController(this));
    }
}

Множественные контроллеры помогают организовать сложную логику компонента, разделяя разные аспекты функциональности: работу с событиями, синхронизацию с сервером, управление внутренним состоянием.


Переиспользуемость контроллеров

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

class AnimationController {
    constructor(host, animationName) {
        this.host = host;
        this.animationName = animationName;
    }

    connectedCallback() {
        this.host.classList.add(this.animationName);
    }

    disconnectedCallback() {
        this.host.classList.remove(this.animationName);
    }
}

Контроллеры и шаблоны

Контроллеры взаимодействуют с шаблонами через свойства компонента или через ref-атрибуты для получения доступа к элементам DOM внутри компонента:

class DomController {
    constructor(host) {
        this.host = host;
    }

    hostUpdated() {
        const input = this.host.shadowRoot.querySelector('input');
        if (input) {
            input.value = this.host.value.toUpperCase();
        }
    }
}

Такой подход позволяет сохранять реактивность и не смешивать логику с шаблоном напрямую.


Применение паттерна в архитектуре

Использование контроллеров повышает читаемость и поддерживаемость компонентов. Логика бизнес-процессов, взаимодействие с API, обработка событий, анимации — всё это можно вынести в контроллеры, оставив компонент тонким и декларативным. В результате проект становится более модульным, тестируемым и удобным для командной работы.

Контроллеры в FAST Element обеспечивают чистое разделение ответственности, делают компоненты гибкими, повторно используемыми и легко расширяемыми без изменения исходного шаблона.