Определение интерфейса для данных формы
Начнём с создания интерфейса для данных нашей формы. Представьте, что мы делаем форму для регистрации со следующими полями:
name(строка)email(строка)password(строка)
Вот как мы можем описать данные формы:
interface RegistrationFormValues {
name: string;
email: string;
password: string;
}
Просто? Да. Полезно? Очень!
Интеграция интерфейса с Formik
Теперь, когда у нас есть интерфейс, давайте используем его в компоненте Formik. Мы можем передать его через generic тип в компонент <Formik>.
import React from 'react';
import { Formik, Form, Field } from 'formik';
interface RegistrationFormValues {
name: string;
email: string;
password: string;
}
const initialValues: RegistrationFormValues = {
name: '',
email: '',
password: '',
};
const RegistrationForm: React.FC = () => {
return (
<Formik<RegistrationFormValues>
initialValues={initialValues}
onSubmit={(values) => {
console.log('Form Data:', values);
}}
>
{() => (
<Form>
<div>
<label htmlFor="name">Name</label>
<Field id="name" name="name" placeholder="Enter your name" />
</div>
<div>
<label htmlFor="email">Email</label>
<Field id="email" name="email" type="email" placeholder="Enter your email" />
</div>
<div>
<label htmlFor="password">Password</label>
<Field id="password" name="password" type="password" placeholder="Enter your password" />
</div>
<button type="submit">Register</button>
</Form>
)}
</Formik>
);
};
export default RegistrationForm;
Что здесь происходит?
Мы передали интерфейс
RegistrationFormValuesчерез<Formik<RegistrationFormValues>>. Это позволяет Formik знать, какие поля присутствуют в нашей форме.initialValuesстрого типизированы. Если вы забудете добавить какое-либо поле или сделаете опечатку, TypeScript сразу выстрелит в вас ошибкой.Поля формы
<Field>, такие какname, типизированы автоматически благодаря привязке к интерфейсу.
Типизация функции onSubmit
Без типизации функции onSubmit можно ненароком забыть, какие данные мы ожидаем. Хорошая новость состоит в том, что Formik уже знает, какие данные ваша форма должна возвращать!
const RegistrationForm: React.FC = () => {
const handleSubmit = (values: RegistrationFormValues) => {
// TypeScript знает, что values соответствует RegistrationFormValues
console.log('Form Submitted:', values);
};
return (
<Formik<RegistrationFormValues>
initialValues={initialValues}
onSubmit={handleSubmit}
>
{/* ... */}
</Formik>
);
};
Теперь, если вы вдруг передадите неправильный интерфейс, TypeScript даст вам об этом знать.
Типизация <Field> и <ErrorMessage>
Formik предоставляет отличные встроенные компоненты, такие как <Field> и <ErrorMessage>. Их тоже можно типизировать!
<Field<RegistrationFormValues['name']>
id="name"
name="name"
placeholder="Enter your name"
/>
Здесь мы указали, что этот <Field> соответствует полю name из интерфейса RegistrationFormValues.
Аналогично можно работать с <ErrorMessage>:
import { ErrorMessage } from 'formik';
<ErrorMessage name="name" render={(msg) => <div className="error">{msg}</div>} />
Компоненты пользовательских полей
Formik позволяет использовать кастомные компоненты вместо <Field>. Давайте создадим кастомное поле для ввода текста:
interface CustomInputProps {
field: {
name: string;
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
onBlur: (e: React.FocusEvent<HTMLInputElement>) => void;
};
form: {
touched: Record<string, boolean>;
errors: Record<string, string>;
};
}
const CustomInput: React.FC<CustomInputProps> = ({ field, form }) => {
return (
<div>
<input {...field} />
{form.touched[field.name] && form.errors[field.name] && (
<div className="error">{form.errors[field.name]}</div>
)}
</div>
);
};
Использование этого компонента:
<Field name="name" component={CustomInput} />
Зачем это нужно?
Использование кастомных полей полезно, если вам нужно сложное поведение или специфические элементы ввода (например, выпадающий список или редактор текста).
Обработка сложных форм (объекты и массивы)
Что если наша форма становится более сложной? Представьте, что мы добавляем динамический массив полей, например, для адресов пользователя:
interface Address {
street: string;
city: string;
}
interface UserFormValues {
name: string;
addresses: Address[];
}
Formik поддерживает работу с объектами и массивами через FieldArray. Вот пример:
import { FieldArray } from 'formik';
const initialValues: UserFormValues = {
name: '',
addresses: [{ street: '', city: '' }],
};
<FieldArray name="addresses">
{({ remove, push }) => (
<>
{values.addresses.map((_, index) => (
<div key={index}>
<Field name={`addresses[${index}].street`} placeholder="Street" />
<Field name={`addresses[${index}].city`} placeholder="City" />
<button type="button" onClick={() => remove(index)}>Remove</button>
</div>
))}
<button type="button" onClick={() => push({ street: '', city: '' })}>
Add Address
</button>
</>
)}
</FieldArray>
Здесь мы добавили поддержку динамического добавления и удаления адресов.
Типичные ошибки и как их избежать
Если вы пропустите какой-либо из ключей интерфейса в initialValues, TypeScript сразу предупредит вас. Но если вы используете тип any, Formik не сможет выгнать вас с работы за ошибки — избегайте его. Ещё одна частая ошибка — забыть передать generic тип в <Formik> и <Field>: это приведёт к потере всех плюсов типизации.
TypeScript иногда может ругаться, что Field не передаёт правильный тип. Если это произошло, проверьте, чтобы имя поля совпадало с ключом интерфейса.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ