Компонентные библиотеки и Design Systems

Lit — лёгкий и современный фреймворк для создания веб-компонентов на основе стандартов платформы. Он особенно хорошо подходит для построения компонентных библиотек и Design System благодаря минимализму, высокой производительности и строгой опоре на Web Components API.


В основе Lit лежат кастомные элементы (Custom Elements) и шаблоны на основе tagged template literals. Это означает, что каждый компонент является полноценным HTML-элементом, зарегистрированным в браузере и изолированным от остального DOM.

Ключевые строительные блоки:

  • LitElement — базовый класс для компонентов
  • html — функция для описания шаблонов
  • реактивные свойства
  • Shadow DOM по умолчанию

Такой подход позволяет создавать компоненты, которые:

  • не зависят от фреймворка-приложения
  • легко переиспользуются
  • совместимы с любыми экосистемами (React, Vue, Angular, Vanilla)

Базовый компонент в Lit

Компонент в Lit — это класс, расширяющий LitElement:

import { LitElement, html, css } from 'lit';

export class MyButton extends LitElement {
  static properties = {
    disabled: { type: Boolean }
  };

  static styles = css`
    button {
      padding: 8px 16px;
      border-radius: 4px;
    }
  `;

  render() {
    return html`
      <button ?disabled=${this.disabled}>
        <slot></slot>
      </button>
    `;
  }
}

customElements.define('my-button', MyButton);

Здесь отражены ключевые идеи Lit:

  • декларативный рендеринг
  • реактивность через свойства
  • инкапсуляция стилей
  • slot-механизм для композиции

Реактивная модель данных

Lit использует реактивность на уровне свойств класса. Любое изменение реактивного свойства автоматически инициирует обновление DOM.

Объявление свойств

static properties = {
  value: { type: String },
  count: { type: Number, reflect: true }
};

Особенности:

  • type управляет преобразованием атрибут ↔︎ свойство
  • reflect: true синхронизирует свойство с HTML-атрибутом
  • поддерживаются Boolean, Number, Object, Array

Обновление DOM

Lit использует fine-grained diffing на уровне шаблона, а не всего компонента. Это снижает количество операций с DOM и делает обновления предсказуемыми.


Шаблоны и директивы

Шаблоны в Lit основаны на tagged template literals:

html`<p>${this.value}</p>`

Условный рендеринг

${this.visible ? html`<div>Content</div>` : null}

Рендеринг списков

import { repeat } from 'lit/directives/repeat.js';

${repeat(this.items, item => item.id, item =>
  html`<li>${item.label}</li>`
)}

Директивы позволяют расширять шаблоны без усложнения логики компонента. Для Design System это критично — шаблоны должны оставаться читаемыми и стабильными.


Стилизация и изоляция

Каждый компонент Lit по умолчанию использует Shadow DOM. Это обеспечивает:

  • отсутствие конфликтов стилей
  • предсказуемый внешний вид
  • локальность CSS

Статические стили

static styles = css`
  :host {
    display: inline-block;
  }
`;

Использование CSS Custom Properties

Для Design System особенно важно использовать CSS-переменные как основной механизм кастомизации:

button {
  background: var(--ds-button-bg, #005fcc);
  color: var(--ds-button-color, #fff);
}

Такой подход:

  • сохраняет инкапсуляцию
  • позволяет централизованно управлять темами
  • не требует проброса свойств через JS

Слоты и композиция компонентов

Слоты — основной механизм композиции в Lit-компонентах:

<my-card>
  <h2 slot="title">Заголовок</h2>
  <p>Контент</p>
</my-card>
render() {
  return html`
    <div class="card">
      <slot name="title"></slot>
      <slot></slot>
    </div>
  `;
}

Для компонентных библиотек это позволяет:

  • разделять структуру и контент
  • создавать гибкие layout-компоненты
  • избегать жёсткой логики внутри компонента

События и взаимодействие

Lit не навязывает собственную систему событий. Используются стандартные DOM Events.

Генерация событий

this.dispatchEvent(new CustomEvent('change', {
  detail: { value: this.value },
  bubbles: true,
  composed: true
}));

Параметры bubbles и composed важны для Design System:

  • событие может выходить за пределы Shadow DOM
  • родительские приложения могут реагировать без знания внутренней структуры

Контролируемые и неконтролируемые компоненты

Lit одинаково хорошо поддерживает оба подхода:

  • контролируемые — состояние управляется извне через свойства
  • неконтролируемые — компонент сам управляет состоянием

Пример гибридного подхода:

updated(changedProps) {
  if (changedProps.has('value')) {
    this.internalValue = this.value;
  }
}

Это позволяет адаптировать компоненты под разные сценарии использования в Design System.


Производительность и обновления

Lit оптимизирован для частых обновлений:

  • обновление происходит асинхронно
  • несколько изменений объединяются в один рендер
  • используется requestUpdate для ручного контроля
this.requestUpdate('value', oldValue);

Для сложных библиотек это даёт возможность:

  • точно контролировать моменты рендера
  • избегать лишних вычислений
  • масштабировать систему без деградации производительности

Lit как основа Design System

Lit особенно хорошо подходит для Design System по следующим причинам:

Стандарты платформы

  • Web Components
  • Shadow DOM
  • Custom Elements

Независимость

  • компоненты не привязаны к конкретному фреймворку
  • возможна интеграция в любые приложения

Чёткие контракты

  • свойства
  • события
  • слоты

Долгосрочная поддержка

  • минимальный API
  • низкий риск устаревания
  • высокая совместимость браузеров

Организация библиотеки компонентов

Типичная структура:

components/
  button/
    button.ts
    button.styles.ts
  input/
  modal/
tokens/
  colors.css
  spacing.css

Рекомендации:

  • разделять логику и стили
  • выносить дизайн-токены в CSS
  • избегать бизнес-логики внутри компонентов

Итоговая роль Lit в экосистеме

Lit занимает нишу фундаментального инструмента для создания компонентных библиотек и Design System. Он не конкурирует с фреймворками приложений, а дополняет их, предоставляя стабильный, предсказуемый и стандартизированный слой UI-компонентов.