Skip to content

Форма, которая не забывает черновик: Form Schema + Storage Kit

Длинная форма — многошаговый визард, объёмная анкета — и пользователь случайно закрывает вкладку или обновляет страницу. Без сохранения черновика всё введённое пропадает. Form Schema не хранит состояние формы сама по себе (оно живёт в реактивном values из useForm(), а не где-то ещё), поэтому подключить постоянное хранилище — это просто следить за values и восстанавливать их при инициализации, а не переписывать логику формы.

Код

vue
<script setup lang="ts">
import { watch } from 'vue'
import { useForm } from '@macrulez/vue-form-schema'
import { useStorage } from 'vue-storage-kit'
import type { FieldDefinition } from '@macrulez/vue-form-schema'

const schema: FieldDefinition[] = [
  { type: 'text', name: 'title', label: 'Заголовок', required: true },
  { type: 'textarea', name: 'body', label: 'Текст' },
]

// TTL — черновик старше суток не восстанавливаем: скорее всего, пользователь
// уже не вспомнит, о чём была форма, и лучше начать с чистого листа.
const { value: draft } = useStorage('post-draft', {
  defaultValue: {} as Record<string, unknown>,
  ttl: 24 * 60 * 60 * 1000,
})

const { values, submit } = useForm({
  schema,
  initialValues: draft.value,
  onSubmit: async (data) => {
    const response = await fetch('/api/posts', { method: 'POST', body: JSON.stringify(data) })
    if (!response.ok) throw new Error(`Не удалось сохранить: ${response.status}`)
    draft.value = {} // черновик отправлен успешно — только теперь очищаем
  },
})

// deep: true — values это плоский объект по полям схемы, но поля типа
// 'group'/'array' дают вложенную структуру, точечное отслеживание её не поймает.
watch(values, (v) => (draft.value = { ...v }), { deep: true })
</script>

Почему watch с копированием, а не прямое присваивание draft.value = values

values из useForm() — Vue-реактивный объект, живущий внутри composable; draft.value из useStorage() — отдельный Ref, который сериализуется в хранилище при каждой записи. Если присвоить draft.value = values один раз при инициализации, draft будет ссылаться на тот же реактивный объект, но useStorage() не узнает о последующих мутациях его полей — запись в хранилище происходит при присваивании .value, а не при мутации того, на что оно указывает. watch с { ...v } на каждое изменение — простой и явный способ гарантировать, что хранилище всегда видит присваивание, а не мутацию.

Что дальше

  • Добавить debounce в опции useStorage() — если черновик пишется на каждое нажатие клавиши, дебаунс избавляет от записи в localStorage на каждый символ. См. Storage Kit — обзор.
  • Для чувствительных данных — encrypt: { password } у useStorage(), чтобы черновик не лежал в localStorage открытым текстом.