vuevaldi
vuevaldi
Библиотека валидации форм для Vue 3.
TypeScript-first, агностик к валидатору (yup работает из коробки), реактивная, событийно-ориентированная.
🌐 Language / Язык: English · Русский
Возможности
- Агностик к валидатору: подходит любой валидатор, реализующий интерфейс
FormValidator; адаптер для yup включён. - Ошибки структурно повторяют модель формы (вложенные объекты), внутри — плоские пути.
- Метки (labels) и значения по умолчанию берутся из самой схемы (yup
.label()/.default()). - Перевалидация при вводе (
validateOnInput) — до submit показываются ошибки только изменённых полей. - События submit:
success,error,finished. - Маппинг серверных ошибок через
errorHandler+ violations. - Написано на TypeScript со строгой типизацией полей, ошибок, меток и дефолтов.
Установка
npm install vuevaldi yup
vue и yup — peer-зависимости. Пакет только ESM, типы включены.
Быстрый старт
Объявляем yup-схему, создаём валидатор через useYupValidator и форму через useForm.
<script setup lang="ts">
import * as yup from 'yup';
import { useForm, useYupValidator } from 'vuevaldi';
const schema = yup.object({
name: yup.string().required('Имя обязательно').label('Имя'),
email: yup.string().email('Некорректный email').required('Email обязателен').label('Email'),
age: yup.number().min(18, 'Должно быть 18+').label('Возраст'),
});
const validator = useYupValidator(schema);
const { model, error, errors, labels, isSubmitting, submit } = useForm({
validator,
validateOnInput: true,
submitHandler: async values => {
await api.save(values);
},
});
</script>
<template>
<form @submit.prevent="submit()">
<p v-if="error" class="form-error">{{ error }}</p>
<div>
<label>{{ labels.name }}</label>
<input v-model="model.name" />
<span v-if="errors.name" class="field-error">{{ errors.name[0] }}</span>
</div>
<div>
<label>{{ labels.email }}</label>
<input v-model="model.email" />
<span v-if="errors.email" class="field-error">{{ errors.email[0] }}</span>
</div>
<div>
<label>{{ labels.age }}</label>
<input v-model.number="model.age" />
<span v-if="errors.age" class="field-error">{{ errors.age[0] }}</span>
</div>
<button type="submit" :disabled="isSubmitting">Отправить</button>
</form>
</template>
model, errors, labels и остальное — Vue-рефы / computed-рефы. При деструктуризации в <script setup> они автоматически разворачиваются в шаблоне. Если держать весь объект целиком (const form = useForm(...)), обращайтесь через .value: form.model.value.name.
submit() сначала валидирует; при успехе вызывает submitHandler(values) и возвращает true, иначе — false.
API — useForm(opt)
Поля FormContextOptions (все опциональны, кроме validator и submitHandler):
| Опция | Тип | Описание |
|---|---|---|
values |
PartialObjectDeep<TFields> |
Реальные начальные данные (например, загруженная запись). Используются как есть, без merge со schema-defaults. |
defaultValues |
PartialObjectDeep<TFields> |
Шаблон начального/сброшенного состояния. Глубоко сливается поверх schema-defaults (schema дозаполняет, эти переопределяют). |
extraData |
TExtraData |
Произвольные дополнительные данные, доступные через getExtraData(). |
submitHandler |
(values: TFields) => TResp | Promise<TResp> |
Обязателен. Вызывается при успешной валидации. |
errorHandler |
(error: TErr) => { message: string; violations?: Violation[] } |
Преобразует ошибку submit в сообщение и ошибки полей. |
resetAfterSubmit |
boolean |
Сбросить форму после успешного submit. |
validateOnInput |
boolean |
Перевалидировать при изменении модели; до первого submit показываются ошибки только изменённых полей. |
validator |
FormValidator<TFields> |
Обязателен. Реализация валидатора. |
type Violation = { message: string; propertyPath: string };
Приоритет начальной модели:
values ?? merge(schemaDefaults, defaultValues) ?? {}
valuesвыигрывают целиком (schema не подмешивается — реальные данные не трогаются).- Иначе база — schema-defaults, поверх глубоко сливается
defaultValues. - Массивы заменяются целиком, не сливаются по индексу.
API — возвращаемый контекст
| Поле | Тип | Описание |
|---|---|---|
model |
Ref<PartialObjectDeep<TFields>> |
Реактивное состояние формы; биндьте инпуты через v-model. |
error |
ComputedRef<string> |
Глобальная ошибка формы (submit / сервер). |
errors |
ComputedRef<ValidationErrors<TFields>> |
Ошибки полей, структурно повторяющие модель. |
labels |
ComputedRef<FormLabels<TFields>> |
Метки полей из схемы, структурно повторяющие модель. |
isSubmitting |
ComputedRef<boolean> |
true, пока идёт submit(). |
isDirty |
ComputedRef<boolean> |
true, если модель отличается от исходного состояния (на момент создания формы или последнего reset()). Сравнение всегда идёт по текущим данным, поэтому ручной возврат значений к исходным (в т.ч. по отдельному полю) тоже сбрасывает флаг в false — не только reset(). |
submit |
(throwError?: boolean) => Promise<boolean> |
Валидация → вызов submitHandler. Возвращает успех. С throwError бросает Error(error) при неудаче. |
reset |
(newOpt?: { values?, defaultValues? }) => void |
Сброс ошибок и модели (с merge schema-defaults как выше). |
validate |
() => Promise<false | TFields> |
Валидирует текущую модель; возвращает распарсенные значения или false. |
addEventListener |
(event, listener) => void |
Подписка на success / error / finished. |
getExtraData |
() => TExtraData |
Возвращает extraData; бросает ошибку, если не задано. |
AnyFormContext — слаботипизированная версия того же контракта (для передачи контекста куда-либо без типов).
Валидатор
Интерфейс FormValidator
Любой валидатор для useForm должен реализовать этот контракт:
interface FormValidator<TFields extends Recordable = Recordable> {
isValid: (values: PartialObjectDeep<TFields>) => Promise<boolean>;
parse: (values: PartialObjectDeep<TFields>) => Promise<
| { isError: false; values: TFields; errors: undefined }
| { isError: true; values: undefined; errors: FlattenedErrors }
>;
describe?: () => FormSchemaInfo<TFields>;
}
errors— плоская карта:Record<string, string[]>(пути вида'address.city'). Форма разворачивает её во вложенный рефerrors.describeопционален и питаетlabels+ schema-defaults.FlattenedErrors = Record<string, string[]>.
useYupValidator(schema, options?)
Оборачивает yup ObjectSchema в FormValidator.
import { useYupValidator } from 'vuevaldi';
const schema = yup.object({
name: yup.string().required().label('Имя'),
age: yup.number().min(18).label('Возраст'),
});
const validator = useYupValidator(schema, {
// ValidateOptions (yup): strict, abortEarly, stripUnknown, recursive...
});
Дефолты: strict: false, abortEarly: false, stripUnknown: false, recursive: true, disableStackTrace: true.
Работает со вложенными объектами, массивами, кортежами, .when() и т.д. — ошибки раскладываются по путям полей и затем собираются обратно в структуру модели.
const schema = yup.object({
profile: yup.object({
firstName: yup.string().required().label('Имя'),
lastName: yup.string().required().label('Фамилия'),
}),
tags: yup.array().of(yup.string()),
items: yup.array().of(yup.object({
sku: yup.string().required().label('Артикул'),
qty: yup.number().min(1).label('Количество'),
})),
});
errors.profile.firstName → string[], errors.items → [{ sku?: string[]; qty?: string[] }].
Метки и дефолты из схемы
useYupValidator отдаёт describe(), построенный из yup schema.describe(), который наполняет две вещи:
-
labels— метки из.label(), структурно какerrors/модель:- поля-листья →
string | undefined; - поля-объекты → вложенный объект меток; собственный label объекта доступен по зарезервированному ключу
$label(labels.money.$label); - массивы примитивов → метка элемента (с фолбэком на метку массива);
- массивы объектов → массив с одним элементом = метки элемента (
labels.items[0].sku); метка самого массива лежит на массиве (labels.items.$label), метка объекта-элемента —labels.items[0].$label.
- поля-листья →
-
defaults— значения из.default(), база для начальной модели иreset()(сливаются подdefaultValues, см. выше).
const schema = yup.object({
name: yup.string().default('Иван').label('Имя'),
address: yup.object({
city: yup.string().default('Москва').label('Город'),
}).label('Адрес'),
});
const { labels, model } = useForm({
validator: useYupValidator(schema),
submitHandler: async () => {},
});
// labels: { name: 'Имя', address: { $label: 'Адрес', city: 'Город' } }
// labels.address.$label → 'Адрес'
// model: { name: 'Иван', address: { city: 'Москва' } }
Кастомные валидаторы могут реализовать describe?: () => FormSchemaInfo<TFields>, чтобы тоже отдавать labels и defaults.
События
Подписка через addEventListener; каждый вызов регистрирует ещё один слушатель.
form.addEventListener('success', data => console.log('Сохранено', data));
form.addEventListener('error', err => console.error('Ошибка', err));
form.addEventListener('finished', () => console.log('Готово'));
| Событие | Payload | Когда |
|---|---|---|
success |
TResp — возвращаемое значение submitHandler |
После успешного submit |
error |
TErr — ошибка валидации ('Validation failed') или ошибка из submitHandler |
При неудачной валидации или submit |
finished |
— | Всегда после попытки submit |
Ошибки при submit
submit() сначала валидирует. При неудачной валидации error получает 'Validation failed', срабатывает событие error, submit() возвращает false. Если валидация прошла — ожидается submitHandler(values):
- при успехе — событие
success(+ сброс, еслиresetAfterSubmit), возвращаетсяtrue; - при ошибке — вызывается
errorHandler(error)(если передан), иначеerror.value = error.messageдля инстансовError.
errorHandler преобразует ошибку в сообщение и ошибки полей, обычно из ответа бэкенда:
import type { AxiosError } from 'axios';
const schema = yup.object({
name: yup.string().required().label('Имя'),
email: yup.string().required().label('Email'),
});
type UserForm = yup.InferType<typeof schema>;
interface ApiError {
message: string;
errors: { field: string; message: string }[];
}
const form = useForm<UserForm, void, AxiosError<ApiError>>({
validator: useYupValidator(schema),
submitHandler: values => api.save(values),
errorHandler: e => ({
message: e.response?.data.message ?? 'Запрос не удался',
violations: e.response?.data.errors.map(v => ({
message: v.message,
propertyPath: v.field, // например 'name' или 'address.city'
})),
}),
});
Violations с propertyPath попадают в ту же вложенную структуру errors, что и ошибки клиентской валидации.
Опции подробнее
validateOnInput
При включении форма глубоко следит за моделью, перевалидирует при каждом изменении и до первого submit показывает ошибки только изменённых полей. После неудачного submit показываются все ошибки.
resetAfterSubmit
Сбрасывает форму (ошибки + модель к дефолтам) после успешного вызова submitHandler.
extraData / getExtraData
Произвольные метаданные, переданные при создании и читаемые через getExtraData() (типизированы как TExtraData). Бросает ошибку, если extraData не передан.
values vs defaultValues
values— реальные данные, используются как есть, без merge со schema-defaults.defaultValues— шаблон, глубоко сливается поверх schema-defaults.- Не передавать ничего — форма инициализируется только из schema-defaults.
Кастомный валидатор
Подойдёт любой объект, соответствующий FormValidator. Минимальный ручной валидатор:
import type { FormValidator } from 'vuevaldi';
type UserForm = {
name: string;
age?: number;
};
const customValidator: FormValidator<UserForm> = {
isValid: async values => Boolean(values.name && values.name.length >= 2),
parse: async values => {
const errors: Record<string, string[]> = {};
if (!values.name) {
errors.name = ['Имя обязательно'];
} else if (values.name.length < 2) {
errors.name = ['Имя должно быть не короче 2 символов'];
}
if (values.age !== undefined && values.age < 18) {
errors.age = ['Должно быть 18+'];
}
if (Object.keys(errors).length > 0) {
return { isError: true, values: undefined, errors };
}
return { isError: false, values: values as UserForm, errors: undefined };
},
};
const form = useForm<UserForm>({
validator: customValidator,
submitHandler: values => api.save(values),
});
Опционально реализуйте describe(), чтобы тоже отдавать labels и schema-defaults:
import type { FormSchemaInfo } from 'vuevaldi';
const customValidator: FormValidator<UserForm> = {
// isValid, parse ...
describe: (): FormSchemaInfo<UserForm> => ({
labels: { name: 'Имя', age: 'Возраст' },
defaults: { name: 'Иван' },
}),
};
Экспортируемые типы
import type {
AnyFormContext, // слаботипизированный контекст
FormContext, // типизированный контекст
FormContextOptions, // опции useForm
FormSchemaInfo, // { labels?, defaults? } из validator.describe
FormValidator, // контракт валидатора
FormLabels, // структура меток
PartialObjectDeep, // глубокий partial полей (массивы не рекурсятся)
ValidationErrors, // структура ошибок
} from 'vuevaldi';
Лицензия
MIT © ТОО «ADEGARA».
Related Packages
Vue Formulario is a library, inspired by Vue Formulate , that handles the core l...
Headless, schema-driven form framework core for Vue 3 — elements, validation, co...
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.14.0 | ^3.0.0 | MIT | |
| 0.13.3 | ^3.0.0 | MIT | |
| 0.13.2 | ^3.0.0 | MIT | |
| 0.13.1 | ^3.0.0 | MIT | |
| 0.13.0 | ^3.0.0 | MIT | |
| 0.12.1 | * | MIT | |
| 0.12.0 | * | MIT | |
| 0.11.7 | * | MIT | |
| 0.11.6 | * | MIT | |
| 0.11.5 | * | MIT | |
| 0.11.4 | * | MIT | |
| 0.11.3 | * | MIT | |
| 0.11.2 | * | MIT | |
| 0.11.1 | * | MIT | |
| 0.11.0 | * | MIT | |
| 0.10.3 | * | MIT | |
| 0.10.2 | * | MIT | |
| 0.10.1 | * | MIT | |
| 0.10.0 | * | MIT | |
| 0.9.1 | * | MIT | |
| 0.9.0 | * | MIT | |
| 0.8.7 | * | MIT | |
| 0.8.6 | * | MIT | |
| 0.8.5 | ^3.4.15 | MIT | |
| 0.8.4 | ^3.4.15 | MIT | |
| 0.8.3 | ^3.4.15 | MIT | |
| 0.8.2 | ^3.4.15 | MIT | |
| 0.8.1 | ^3.4.15 | MIT | |
| 0.8.0 | ^3.4.15 | MIT | |
| 0.7.5 | ^3.4.15 | MIT |
Showing the latest 30 of 46. See every release on npm