Разделение логики и представления

Slim.js — это легковесная библиотека для создания веб-компонентов, которая активно использует подход декларативного программирования и двухстороннего связывания данных. Одной из ключевых практик при работе с библиотекой является разделение логики и представления, что обеспечивает удобство поддержки кода, масштабируемость и тестируемость приложений.

Компоненты Slim.js как единицы логики и представления

В Slim.js каждый компонент объединяет шаблон разметки и бизнес-логику. Основной класс компонента наследуется от Slim, а шаблон определяется через HTML в виде строки или через тег <template>. Логика компонента реализуется в виде методов и свойств класса.

import { Slim } from 'slim-js';

class CounterComponent extends Slim {
  count = 0;

  increment() {
    this.count++;
  }

  decrement() {
    this.count--;
  }
}

Slim.define('counter-component', CounterComponent, `
  <div>
    <button click="decrement">-</button>
    <span>{count}</span>
    <button click="increment">+</button>
  </div>
`);

В этом примере шаблон сосредоточен исключительно на отображении данных (count) и реакциях на события (click), тогда как бизнес-логика (increment, decrement) полностью отделена в методы класса. Такой подход позволяет легко модифицировать интерфейс без изменения внутренней логики.

Декларативное связывание данных

Slim.js поддерживает одно- и двустороннее связывание данных с использованием фигурных скобок {} и директив событий, таких как click. Это позволяет разделить:

  • Представление — HTML-шаблон с динамическими вставками.
  • Логику — методы и свойства, отвечающие за состояние и обработку событий.
<span>{count}</span>

Эта запись автоматически обновляет отображение при изменении значения свойства count, без необходимости вручную обращаться к DOM. Логика управления состоянием полностью скрыта за методами компонента.

Методы и жизненный цикл компонентов

Slim.js предоставляет несколько жизненных циклов компонентов, через которые можно организовать логику:

  • onBeforeCreated() — выполняется перед инициализацией DOM. Используется для подготовки данных.
  • onCreated() — выполняется после инициализации. Подходит для подписок на события или инициализации сервисов.
  • onBeforeUpdate() и onUpdated() — позволяют обрабатывать изменения данных и выполнять действия до или после обновления DOM.

Пример разделения логики через жизненный цикл:

class TimerComponent extends Slim {
  time = 0;
  intervalId = null;

  onCreated() {
    this.startTimer();
  }

  startTimer() {
    this.intervalId = setInterval(() => {
      this.time++;
    }, 1000);
  }

  stopTimer() {
    clearInterval(this.intervalId);
  }
}

Slim.define('timer-component', TimerComponent, `
  <div>
    <span>{time}</span>
    <button click="stopTimer">Stop</button>
  </div>
`);

Шаблон остаётся чистым и не содержит логики управления таймером, которая полностью вынесена в методы компонента.

Использование реактивных свойств

Slim.js автоматически отслеживает изменения свойств класса, что позволяет строить реактивные интерфейсы без прямого вмешательства в DOM. Свойства объявляются как обычные поля класса, и любое их изменение мгновенно отражается в представлении. Это укрепляет принцип разделения ответственности:

  • Логика управления состоянием внутри класса.
  • Представление через HTML-шаблон.
class ToggleComponent extends Slim {
  isActive = false;

  toggle() {
    this.isActive = !this.isActive;
  }
}

Slim.define('toggle-component', ToggleComponent, `
  <div>
    <button click="toggle">{isActive ? 'Active' : 'Inactive'}</button>
  </div>
`);

Организация сложных компонентов

Для больших приложений рекомендуется разбивать логику на несколько вложенных компонентов. Slim.js поддерживает композицию компонентов, что позволяет:

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

Пример:

Slim.define('user-card', class extends Slim {
  user = { name: 'Alice', age: 25 };
}, `
  <div>
    <user-name name="{user.name}"></user-name>
    <user-age age="{user.age}"></user-age>
  </div>
`);

Slim.define('user-name', class extends Slim {}, `<span>{name}</span>`);
Slim.define('user-age', class extends Slim {}, `<span>{age} years</span>`);

В этом примере user-card отвечает за данные пользователя, а дочерние компоненты полностью сосредоточены на отображении конкретных свойств. Такой подход облегчает поддержку и тестирование.

Принципы эффективного разделения

  1. Логика должна находиться в методах и свойствах класса, а шаблон — только для отображения.
  2. Использовать реактивные свойства для автоматического обновления интерфейса.
  3. Разбивать крупные компоненты на мелкие, концентрируя каждый на одной задаче.
  4. Выносить сторонние сервисы и сложные вычисления за пределы шаблона.
  5. Минимизировать код в шаблоне, оставляя в нём только привязки данных и обработку событий.

Эти практики обеспечивают поддержку принципов MVC и реактивного программирования внутри Slim.js, делая код чистым, понятным и легко расширяемым.