Multi-step формы

Multi-step формы — это последовательность экранов или шагов, каждый из которых собирает часть данных от пользователя. В SvelteKit их реализация строится на комбинации реактивных компонентов, состояния приложения и интерактивной навигации между шагами. Основная цель — разделить большие формы на управляемые части, улучшить UX и минимизировать вероятность ошибок ввода.

Структура компонентов

Форма обычно делится на несколько компонентов:

  1. Root-компонент — управляет текущим шагом и глобальным состоянием формы.
  2. Step-компоненты — представляют отдельные шаги формы, отвечают за локальные поля ввода и валидацию.
  3. Navigation-компонент — кнопки “Далее”, “Назад”, “Отправить”, которые взаимодействуют с состоянием root-компонента.

Пример базовой структуры:

<script lang="ts">
  import StepOne from './StepOne.svelte';
  import StepTwo from './StepTwo.svelte';
  import StepThree from './StepThree.svelte';
  
  let currentStep = 1;
  let formData = {
    name: '',
    email: '',
    password: ''
  };

  function nextStep() {
    if (currentStep < 3) currentStep += 1;
  }

  function prevStep() {
    if (currentStep > 1) currentStep -= 1;
  }

  function handleSubmit() {
    console.log('Отправка данных', formData);
  }
</script>

{#if currentStep === 1}
  <StepOne bind:data={formData} />
{:else if currentStep === 2}
  <StepTwo bind:data={formData} />
{:else if currentStep === 3}
  <StepThree bind:data={formData} />
{/if}

<div class="navigation">
  {#if currentStep > 1}
    <button on:click={prevStep}>Назад</button>
  {/if}
  {#if currentStep < 3}
    <button on:click={nextStep}>Далее</button>
  {:else}
    <button on:click={handleSubmit}>Отправить</button>
  {/if}
</div>

Реактивное состояние и двусторонняя привязка

Ключевой момент в multi-step формах — сохранение данных между шагами. В SvelteKit это достигается через bind:

<input type="text" bind:value={data.name} placeholder="Имя" />

Такой подход позволяет Step-компоненту напрямую менять объект formData в родителе, без необходимости прокидывать колбэки на каждый ввод.

Валидация на каждом шаге

Для контроля корректности данных удобно использовать локальные валидаторы, которые блокируют переход на следующий шаг:

<script lang="ts">
  export let data;

  let errors = { name: '', email: '' };

  function validateStep() {
    errors.name = data.name ? '' : 'Имя обязательно';
    errors.email = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email) ? '' : 'Неверный email';
    return !errors.name && !errors.email;
  }
</script>

<input type="text" bind:value={data.name} placeholder="Имя" />
{#if errors.name}<span class="error">{errors.name}</span>{/if}

<input type="email" bind:value={data.email} placeholder="Email" />
{#if errors.email}<span class="error">{errors.email}</span>{/if}

<button on:click={() => validateStep() && $parent.nextStep()}>Далее</button>

Такой подход позволяет изолировать логику валидации каждого шага и централизованно управлять навигацией через методы root-компонента.

Асинхронная валидация и интеграция с API

Для проверки данных на сервере (например, уникальности email) можно использовать асинхронные функции:

<script lang="ts">
  import { tick } from 'svelte';
  export let data;
  let emailError = '';

  async function checkEmail() {
    const res = await fetch(`/api/check-email?email=${data.email}`);
    const { available } = await res.json();
    emailError = available ? '' : 'Email уже используется';
    await tick();
    return !emailError;
  }
</script>

<input type="email" bind:value={data.email} placeholder="Email" on:blur={checkEmail} />
{#if emailError}<span class="error">{emailError}</span>{/if}

Использование библиотек UI

SvelteKit поддерживает интеграцию с различными UI-библиотеками, такими как Svelte Material UI, Flowbite Svelte или Shadcn UI, что позволяет создавать стильные формы с минимальными усилиями:

  • Компоненты ввода (TextField, Select, Checkbox) легко оборачиваются в Step-компоненты.
  • Стилизация и анимации переходов между шагами делается через стандартные Svelte transitions или готовые UI-анимации.
  • Некоторые библиотеки предоставляют готовые Stepper-компоненты, которые визуально отображают прогресс по шагам формы.

Пример интеграции с шаговым UI-компонентом:

<Stepper current={currentStep}>
  <Step title="Профиль" />
  <Step title="Контакты" />
  <Step title="Подтверждение" />
</Stepper>

Управление прогрессом

Прогресс-бар для multi-step формы повышает удобство пользователя:

<div class="progress-bar">
  <div class="progress" style="width: {(currentStep / totalSteps) * 100}%"></div>
</div>

Поддержка динамического обновления состояния и реактивность Svelte делают этот процесс простым и элегантным.

Поддержка сохранения состояния

Для форм с большим количеством шагов полезно сохранять данные в localStorage или Svelte store, чтобы пользователь мог вернуться к заполнению после перерыва:

import { writable } from 'svelte/store';

export const formDataStore = writable({
  name: '',
  email: '',
  password: ''
});

formDataStore.subscribe(value => {
  localStorage.setItem('multiStepForm', JSON.stringify(value));
});

Анимации перехода между шагами

SvelteKit позволяет использовать встроенные анимации для плавного перехода между шагами:

<script>
  import { fade } from 'svelte/transition';
</script>

{#if currentStep === 1}
  <StepOne bind:data={formData} in:fade out:fade />
{/if}

Это делает интерфейс более динамичным и улучшает восприятие многослойных форм.

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

  • Разделение формы на root-компонент + step-компоненты обеспечивает ясную структуру и простое управление состоянием.
  • Bind и реактивные переменные позволяют синхронизировать данные между шагами без лишних колбэков.
  • Валидация и асинхронная проверка гарантируют корректность данных до отправки на сервер.
  • Интеграция с UI-библиотеками ускоряет создание визуально привлекательных форм.
  • Хранение состояния в store или localStorage повышает устойчивость форм к прерыванию ввода.

Эти подходы позволяют строить сложные, интерактивные multi-step формы в SvelteKit, которые остаются управляемыми, масштабируемыми и удобными для пользователя.