Отправка форм

SkateJS — это легковесная библиотека для создания веб-компонентов с использованием чистого JavaScript. Одним из частых сценариев является работа с формами: отправка данных, управление состоянием полей, обработка событий и интеграция с внешними API. SkateJS предоставляет удобные механизмы для построения декларативных компонентов форм, поддерживая реактивные свойства и события.


Определение формы как компонента

Форма в SkateJS реализуется как веб-компонент с собственным классом. Для этого используется функция define из SkateJS:

import { define, Component, h } from 'skatejs';

class UserForm extends Component {
  static get props() {
    return {
      username: { attribute: true },
      email: { attribute: true },
      submitted: { type: Boolean }
    };
  }

  handleSubmit(event) {
    event.preventDefault();
    this.submitted = true;
    console.log('Отправка данных:', {
      username: this.username,
      email: this.email
    });
  }

  render() {
    return (
      <form onSub mit={this.handleSubmit.bind(this)}>
        <label>
          Имя:
          <input type="text" value={this.username} onIn put={e => this.username = e.target.value} />
        </label>
        <label>
          Email:
          <input type="email" value={this.email} onIn put={e => this.email = e.target.value} />
        </label>
        <button type="submit">Отправить</button>
        {this.submitted && <p>Форма отправлена</p>}
      </form>
    );
  }
}

define('user-form', UserForm);

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

  • Свойства компонента (props) автоматически синхронизируются с атрибутами HTML.
  • Событие onInput позволяет реактивно обновлять состояние полей.
  • Метод handleSubmit предотвращает стандартное поведение формы и реализует кастомную логику отправки.

Управление состоянием полей

SkateJS позволяет использовать реактивные свойства для управления состоянием. Каждое изменение свойства автоматически инициирует повторный рендер компонента. Это удобно для валидации и динамического отображения сообщений об ошибках:

static get props() {
  return {
    username: { attribute: true },
    email: { attribute: true },
    errors: { type: Object, default: () => ({}) }
  };
}

validate() {
  const errors = {};
  if (!this.username) errors.username = 'Имя обязательно';
  if (!this.email) errors.email = 'Email обязателен';
  this.errors = errors;
  return Object.keys(errors).length === 0;
}

handleSubmit(event) {
  event.preventDefault();
  if (this.validate()) {
    console.log('Данные корректны:', { username: this.username, email: this.email });
  }
}

Особенности подхода:

  • Использование объекта errors позволяет централизованно управлять состоянием ошибок.
  • Любое изменение свойства errors приводит к обновлению UI без дополнительных вызовов.

Работа с событиями

SkateJS поддерживает стандартные события DOM, а также позволяет создавать кастомные события для коммуникации между компонентами:

handleSubmit(event) {
  event.preventDefault();
  if (this.validate()) {
    this.dispatchEvent(new CustomEvent('form-submitted', {
      detail: { username: this.username, email: this.email },
      bubbles: true,
      composed: true
    }));
  }
}

Важно:

  • bubbles: true позволяет событию подниматься по дереву DOM.
  • composed: true обеспечивает прохождение события через shadow DOM, если компонент использует изоляцию.

Интеграция с внешними API

Отправка формы на сервер обычно требует работы с fetch или другой библиотекой HTTP-запросов:

async handleSubmit(event) {
  event.preventDefault();
  if (!this.validate()) return;

  try {
    const response = await fetch('/api/submit', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ username: this.username, email: this.email })
    });

    if (!response.ok) throw new Error('Ошибка при отправке данных');

    const result = await response.json();
    console.log('Ответ сервера:', result);
    this.submitted = true;
  } catch (err) {
    this.errors.submit = err.message;
  }
}

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

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

Динамические формы и повторяющиеся поля

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

static get props() {
  return {
    items: { type: Array, default: () => [''] }
  };
}

addItem() {
  this.items = [...this.items, ''];
}

updateItem(index, value) {
  const newItems = [...this.items];
  newItems[index] = value;
  this.items = newItems;
}

render() {
  return (
    <form onSub mit={this.handleSubmit.bind(this)}>
      {this.items.map((item, index) => (
        <input
          key={index}
          value={item}
          onIn put={e => this.updateItem(index, e.target.value)}
        />
      ))}
      <button type="button" onCl ick={() => this.addItem()}>Добавить поле</button>
      <button type="submit">Отправить</button>
    </form>
  );
}

Преимущества такого подхода:

  • Компонент остается реактивным даже при изменении количества полей.
  • Использование key предотвращает ненужный пересоздание элементов.

Валидация на уровне компонента

SkateJS позволяет объединять визуальную часть и логику валидации внутри компонента. Это дает преимущества по поддержке и повторному использованию:

validateField(name, value) {
  switch(name) {
    case 'username':
      return value.length > 0 ? '' : 'Имя обязательно';
    case 'email':
      return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ? '' : 'Некорректный email';
  }
}

onInputChange(name, value) {
  this[name] = value;
  const error = this.validateField(name, value);
  this.errors = { ...this.errors, [name]: error };
}

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

  • Валидация выполняется при каждом вводе, что улучшает UX.
  • Использование отдельного метода validateField делает код чистым и поддерживаемым.

Реактивное отображение ошибок

Ошибки можно отображать напрямую в шаблоне:

<label>
  Email:
  <input type="email" value={this.email} onIn put={e => this.onInputChange('email', e.target.value)} />
  {this.errors.email && <span class="error">{this.errors.email}</span>}
</label>

Преимущества:

  • Автоматическое обновление при изменении errors.email.
  • Встроенная реактивность SkateJS исключает необходимость ручного управления DOM.

Поддержка нескольких форм на странице

SkateJS обеспечивает изоляцию состояния компонентов. Несколько форм одного типа могут существовать на странице без конфликтов:

<user-form></user-form>
<user-form></user-form>

Каждый компонент имеет собственный экземпляр состояния (props) и не зависит от других.


События для внешнего взаимодействия

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

this.dispatchEvent(new CustomEvent('user-data', {
  detail: { username: this.username, email: this.email },
  bubbles: true,
  composed: true
}));

Это позволяет строить сложные интерфейсы, где родительская логика не вмешивается в внутреннее состояние формы.


Итоговый подход к построению форм

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

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