Работа с формами и пользовательским вводом

В Lit формы и пользовательский ввод обрабатываются через стандартные HTML-элементы <form>, <input>, <textarea>, <select> и их события. Основной механизм взаимодействия с формами заключается в связывании значений полей с состоянием компонента и отслеживании изменений через реактивные свойства.

Для определения реактивного состояния используется декоратор @property или функция static properties. Например:

import { LitElement, html, css } from 'lit';
import { property } from 'lit/decorators.js';

class MyForm extends LitElement {
  @property({ type: String }) name = '';
  @property({ type: Number }) age = 0;

  handleInput(e) {
    const { name, value } = e.target;
    this[name] = name === 'age' ? Number(value) : value;
  }

  render() {
    return html`
      <form @submit=${this.handleSubmit}>
        <label>
          Имя:
          <input name="name" .value=${this.name} @input=${this.handleInput} />
        </label>
        <label>
          Возраст:
          <input name="age" type="number" .value=${this.age} @input=${this.handleInput} />
        </label>
        <button type="submit">Отправить</button>
      </form>
      <p>Введено имя: ${this.name}, возраст: ${this.age}</p>
    `;
  }

  handleSubmit(e) {
    e.preventDefault();
    console.log({ name: this.name, age: this.age });
  }
}

customElements.define('my-form', MyForm);

Ключевые моменты:

  • .value связывает значение инпута с реактивным свойством. Префикс . указывает на установку свойства элемента, а не атрибута.
  • Событие @input позволяет отслеживать каждое изменение поля ввода.
  • preventDefault() в submit предотвращает стандартное поведение формы.

Валидация пользовательского ввода

Lit не предоставляет встроенной системы валидации, поэтому используются стандартные HTML-методы и кастомная логика. Например:

handleSubmit(e) {
  e.preventDefault();
  if (!this.name.trim()) {
    alert('Имя обязательно');
    return;
  }
  if (this.age < 0 || this.age > 120) {
    alert('Возраст должен быть от 0 до 120');
    return;
  }
  console.log({ name: this.name, age: this.age });
}

Можно также использовать свойства required, min, max для HTML-элементов:

<input name="age" type="number" .value=${this.age} @input=${this.handleInput} min="0" max="120" required />

Связывание с checkbox и radio

Для чекбоксов и радио важно использовать свойство checked, а не value. Пример:

@property({ type: Boolean }) subscribed = false;

handleCheckbox(e) {
  this.subscribed = e.target.checked;
}

render() {
  return html`
    <label>
      Подписка:
      <input type="checkbox" .checked=${this.subscribed} @change=${this.handleCheckbox} />
    </label>
    <p>Подписка: ${this.subscribed ? 'Да' : 'Нет'}</p>
  `;
}

Для радиокнопок:

@property({ type: String }) gender = '';

handleRadio(e) {
  this.gender = e.target.value;
}

render() {
  return html`
    <label>
      Мужчина
      <input type="radio" name="gender" value="male" .checked=${this.gender === 'male'} @change=${this.handleRadio} />
    </label>
    <label>
      Женщина
      <input type="radio" name="gender" value="female" .checked=${this.gender === 'female'} @change=${this.handleRadio} />
    </label>
    <p>Выбран пол: ${this.gender}</p>
  `;
}

Работа с <select> и списками

Для выпадающих списков используется привязка свойства .value к реактивному свойству:

@property({ type: String }) country = '';

handleSelect(e) {
  this.country = e.target.value;
}

render() {
  return html`
    <label>
      Страна:
      <select .value=${this.country} @change=${this.handleSelect}>
        <option value="">Выберите...</option>
        <option value="ru">Россия</option>
        <option value="us">США</option>
        <option value="fr">Франция</option>
      </select>
    </label>
    <p>Выбрана страна: ${this.country}</p>
  `;
}

Для динамических списков можно использовать массивы и map:

@property({ type: Array }) countries = ['Россия', 'США', 'Франция'];
@property({ type: String }) selectedCountry = '';

render() {
  return html`
    <select .value=${this.selectedCountry} @change=${e => this.selectedCountry = e.target.value}>
      ${this.countries.map(c => html`<option value=${c}>${c}</option>`)}
    </select>
    <p>Выбрана страна: ${this.selectedCountry}</p>
  `;
}

Управление сложными формами

Для больших форм с множеством полей удобно объединять состояние в объект:

@property({ type: Object }) formData = { name: '', age: 0, email: '' };

handleInput(e) {
  const { name, value } = e.target;
  this.formData = { ...this.formData, [name]: value };
}

render() {
  return html`
    <form @submit=${this.handleSubmit}>
      <input name="name" .value=${this.formData.name} @input=${this.handleInput} />
      <input name="age" type="number" .value=${this.formData.age} @input=${this.handleInput} />
      <input name="email" type="email" .value=${this.formData.email} @input=${this.handleInput} />
      <button type="submit">Отправить</button>
    </form>
    <pre>${JSON.stringify(this.formData, null, 2)}</pre>
  `;
}

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


Отслеживание изменений и реактивное обновление

Lit автоматически обновляет DOM при изменении реактивных свойств. Для сложных форм это позволяет отображать ошибки валидации, активировать кнопки отправки или показывать подсказки:

@property({ type: Boolean }) isValid = false;

updated(changedProperties) {
  if (changedProperties.has('formData')) {
    this.isValid = this.formData.name.trim() !== '' && this.formData.age > 0;
  }
}

render() {
  return html`
    <button ?disabled=${!this.isValid}>Отправить</button>
  `;
}

Использование реактивных свойств совместно с событиями формы обеспечивает полный контроль над состоянием и визуальным представлением данных.