Кастомные элементы форм

Riot.js позволяет создавать легковесные, переиспользуемые компоненты с минимальным количеством кода, что делает работу с кастомными элементами форм особенно удобной. Формы в веб-приложениях часто требуют нестандартного поведения: валидации, динамических списков, интерактивных элементов. Кастомные компоненты упрощают эту задачу, предоставляя мощный инструмент для инкапсуляции логики и стилей.

Создание кастомного элемента формы

Кастомный элемент создаётся с помощью функции riot.component() или с использованием старого синтаксиса тегов .riot. Простейший пример кастомного текстового поля:

<text-input>
  <input type="text" onin put={updateValue} value={value}>

  <script>
    export default {
      value: '',
      updateValue(e) {
        this.value = e.target.value
        this.emit('change', this.value)
      }
    }
  </script>
</text-input>

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

  • export default содержит состояние компонента и методы.
  • Событие oninput привязано к функции updateValue, которая обновляет локальное состояние и эмиттирует событие наружу.
  • Метод emit позволяет родительскому компоненту реагировать на изменения значения.

Связывание данных (Two-way binding)

Для форм важно синхронизировать состояние между компонентом и его родителем. В Riot.js используется механизм свойств (props) и событий:

<custom-checkbox checked={isChecked} oncha nge={toggleCheck}></custom-checkbox>

Внутри компонента:

<checkbox>
  <input type="checkbox" checked={props.checked} oncha nge={handleChange}>

  <script>
    export default {
      handleChange(e) {
        this.emit('change', e.target.checked)
      }
    }
  </script>
</checkbox>

Особенности связывания:

  • props передаются при создании компонента и доступны через props.
  • Изменение состояния компонента требует эмита события, чтобы родитель смог обновить свои данные.
  • Прямое изменение props запрещено — только локальная копия состояния.

Валидация и динамическая логика

Кастомные элементы позволяют инкапсулировать правила валидации:

<email-input>
  <input type="email" value={value} onin put={validateEmail}>
  <span if={error} class="error">{error}</span>

  <script>
    export default {
      value: '',
      error: '',
      validateEmail(e) {
        this.value = e.target.value
        const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
        this.error = pattern.test(this.value) ? '' : 'Некорректный email'
        this.emit('change', { value: this.value, valid: !this.error })
      }
    }
  </script>
</email-input>

Важные аспекты:

  • Компонент сам проверяет корректность введённых данных.
  • Состояние ошибки хранится внутри компонента.
  • Родитель получает не только значение, но и информацию о валидности.

Комбинирование компонентов

Формы часто требуют объединения нескольких кастомных элементов:

<login-form>
  <email-input oncha nge={updateEmail}></email-input>
  <password-input oncha nge={updatePassword}></password-input>
  <button oncl ick={submitForm}>Войти</button>

  <script>
    export default {
      email: '',
      password: '',
      updateEmail(e) { this.email = e.value },
      updatePassword(e) { this.password = e.value },
      submitForm() {
        console.log({ email: this.email, password: this.password })
      }
    }
  </script>
</login-form>

Принципы работы:

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

Поддержка динамических списков и вложенных форм

Кастомные компоненты форм могут использоваться внутри циклов each для динамических полей:

<tags-input>
  <div each={tag, i in tags}>
    <input type="text" value={tag} onin put={e => updateTag(e, i)}>
    <button oncl ick={e => removeTag(i)}>Удалить</button>
  </div>
  <button oncl ick={addTag}>Добавить тег</button>

  <script>
    export default {
      tags: [''],
      addTag() { this.tags = [...this.tags, ''] },
      removeTag(i) { this.tags = this.tags.filter((_, index) => index !== i) },
      updateTag(e, i) {
        this.tags[i] = e.target.value
        this.update()
      }
    }
  </script>
</tags-input>

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

  • Компонент может хранить массив значений и управлять ими.
  • Использование each позволяет динамически добавлять и удалять поля.
  • Метод update() вручную обновляет компонент после изменения массива.

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

Каждый кастомный элемент Riot.js имеет собственный CSS:

<text-input>
  <input type="text" class="input">

  <style>
    .input {
      border: 1px solid #ccc;
      padding: 6px 10px;
      border-radius: 4px;
      font-size: 14px;
    }
    .input:focus {
      border-color: #007bff;
      outline: none;
    }
  </style>
</text-input>

Выводы по стилям:

  • Стили изолированы внутри компонента.
  • Нет необходимости придумывать уникальные классы для каждого инстанса.
  • Это обеспечивает чистоту и предсказуемость внешнего вида формы.

Интеграция с внешними библиотеками

Riot.js-компоненты легко интегрируются с другими библиотеками, например, для масок ввода или сложной валидации:

<phone-input>
  <input ref="phone" onin put={formatPhone}>
  <script>
    import IMask from 'imask';
    export default {
      onMounted() {
        this.mask = IMask(this.$refs.phone, { mask: '+{7}(000)000-00-00' });
      },
      formatPhone() {
        this.emit('change', this.$refs.phone.value)
      }
    }
  </script>
</phone-input>

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

  • Использование ref позволяет напрямую работать с DOM-элементами.
  • Внешние библиотеки могут быть встроены без нарушения реактивности.
  • Компонент остаётся автономным и переиспользуемым.

Кастомные элементы форм в Riot.js обеспечивают мощный механизм для построения интерактивных, динамических и переиспользуемых интерфейсов с минимальной нагрузкой на код и с высокой читаемостью. Их структура позволяет сочетать реактивное поведение, валидацию, динамическое управление состоянием и интеграцию со сторонними инструментами.