Валидация данных

Валидация данных в Lit тесно связана с реактивностью компонентов и использованием свойств (properties). Свойства компонентов определяются с указанием типа и дополнительных опций, что позволяет встроить базовую проверку данных.

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

class MyComponent extends LitElement {
  @property({ type: String })
  username = '';

  @property({ type: Number })
  age = 0;
}
customElements.define('my-component', MyComponent);

Типизация свойств позволяет Lit автоматически преобразовывать значения атрибутов в нужный тип, что является первой линией защиты от некорректных данных. Например, если указано { type: Number }, значение атрибута будет приведено к числу.


Кастомная валидация через сеттеры

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

@property({ type: String })
set username(value) {
  if (typeof value !== 'string' || value.trim() === '') {
    console.warn('Некорректное имя пользователя');
    return;
  }
  const oldValue = this._username;
  this._username = value;
  this.requestUpdate('username', oldValue);
}
get username() {
  return this._username;
}

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


Валидация пользовательских форм

Для компонентов с формами рекомендуется комбинировать стандартные HTML-валидации и кастомные методы. Lit позволяет легко привязывать обработчики событий к элементам формы.

render() {
  return html`
    <form @submit="${this.handleSubmit}">
      <input type="text" .value="${this.username}" @input="${this.handleInput}" required />
      <input type="number" .value="${this.age}" @input="${this.handleInput}" min="0" max="120" />
      <button type="submit">Отправить</button>
    </form>
  `;
}

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

handleSubmit(e) {
  e.preventDefault();
  if (!this.validate()) {
    console.warn('Данные не прошли проверку');
    return;
  }
  console.log('Данные корректны', { username: this.username, age: this.age });
}

validate() {
  if (this.username.trim() === '') return false;
  if (this.age < 0 || this.age > 120) return false;
  return true;
}

В этом примере HTML-атрибуты required, min и max дают базовую проверку, а метод validate() обеспечивает дополнительную бизнес-логику. Такой подход делает валидацию гибкой и настраиваемой.


Декораторы для автоматической валидации

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

function validateLength(min, max) {
  return function (proto, propName) {
    let value = proto[propName];
    Object.defineProperty(proto, propName, {
      get() {
        return value;
      },
      set(newValue) {
        if (newValue.length < min || newValue.length > max) {
          console.warn(`Значение ${propName} должно быть от ${min} до ${max} символов`);
          return;
        }
        value = newValue;
        this.requestUpdate(propName);
      }
    });
  };
}

class UserForm extends LitElement {
  @validateLength(3, 15)
  username = '';
}
customElements.define('user-form', UserForm);

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


Асинхронная валидация

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

async checkUsernameAvailable(username) {
  const response = await fetch(`/api/check-username?username=${username}`);
  const { available } = await response.json();
  return available;
}

async handleInput(e) {
  const value = e.target.value;
  if (await this.checkUsernameAvailable(value)) {
    this.username = value;
  } else {
    console.warn('Имя пользователя уже занято');
  }
}

Асинхронная валидация требует аккуратного управления состоянием и отображения ошибок, чтобы не блокировать UI и не создавать гонки данных.


Интеграция с реактивными формами

Lit отлично сочетается с реактивными подходами к формам, например, хранением состояния в объекте formState и автоматическим обновлением компонентов при изменении данных.

formState = {
  username: '',
  age: 0,
  errors: {}
};

handleInput(e) {
  const { name, value } = e.target;
  this.formState[name] = value;
  this.formState.errors[name] = this.validateField(name, value);
  this.requestUpdate();
}

validateField(name, value) {
  switch (name) {
    case 'username':
      return value.trim() === '' ? 'Имя обязательно' : '';
    case 'age':
      return value < 0 || value > 120 ? 'Возраст должен быть от 0 до 120' : '';
    default:
      return '';
  }
}

Подход с централизованным состоянием облегчает отображение ошибок и последующую обработку формы.


Валидация вложенных и сложных данных

В компонентах, где свойства могут быть объектами или массивами, рекомендуется создавать отдельные методы проверки структуры данных. Это позволяет избегать неконсистентного состояния и поддерживать целостность данных.

@property({ type: Array })
items = [];

validateItems() {
  return this.items.every(item => item.name && item.quantity > 0);
}

addItem(item) {
  if (this.validateItem(item)) {
    this.items = [...this.items, item];
  } else {
    console.warn('Некорректный элемент');
  }
}

Выводы по архитектуре валидации в Lit

  • Валидация должна быть встроена на уровне свойств и форм.
  • Сеттеры и декораторы обеспечивают контроль данных при их установке.
  • Асинхронные проверки должны быть аккуратно интегрированы с UI.
  • Централизованное состояние формы и методы проверки отдельных полей упрощают обработку ошибок.
  • Проверка вложенных и сложных структур данных предотвращает неконсистентность и ошибки на стадии рендеринга.