Контролируемые и неконтролируемые компоненты
Контролируемый компонент (controlled) хранит значение в React state и управляет им через обработчик; неконтролируемый (uncontrolled) — хранит значение в DOM, React читает его через ref.
Зачем нужно
Различие между двумя подходами к формам — один из первых вопросов собеседований. Controlled компоненты — стандарт React-форм: значение всегда в state, валидация в реальном времени, условное отображение. Uncontrolled проще для простых случаев (файловый инпут, интеграция со сторонними библиотеками). Смешение подходов без понимания — источник распространённых багов.
Где используется
- Controlled: формы с валидацией, зависимые поля (выбор города зависит от страны), форматирование при вводе
- Uncontrolled: файловые инпуты (
<input type="file">), интеграция с jQuery/сторонними библиотеками, простые формы без валидации - react-hook-form — использует uncontrolled + ref для лучшей производительности (нет ре-рендера при каждом нажатии)
Controlled Component
import { useState } from 'react';
function ControlledForm() {
// Значение хранится в React state
const [form, setForm] = useState({
email: '',
password: '',
});
const [errors, setErrors] = useState({});
const handleChange = (field) => (e) => {
const value = e.target.value;
setForm(prev => ({ ...prev, [field]: value }));
// Валидация в реальном времени — возможна только в controlled
if (field === 'email' && value && !value.includes('@')) {
setErrors(prev => ({ ...prev, email: 'Некорректный email' }));
} else {
setErrors(prev => ({ ...prev, [field]: null }));
}
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('Отправка:', form); // актуальные данные доступны в state
};
return (
<form onSubmit={handleSubmit}>
<div>
{/* value + onChange = controlled input */}
<input
type="email"
value={form.email} // значение из state
onChange={handleChange('email')} // обновляем state
placeholder="Email"
/>
{errors.email && <span className="error">{errors.email}</span>}
</div>
<div>
<input
type="password"
value={form.password}
onChange={handleChange('password')}
placeholder="Пароль"
/>
</div>
<button type="submit">Войти</button>
</form>
);
}
Uncontrolled Component
import { useRef } from 'react';
function UncontrolledForm() {
// Значения живут в DOM, React только читает их через ref
const emailRef = useRef(null);
const passwordRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
// Читаем значения напрямую из DOM элементов
const email = emailRef.current.value;
const password = passwordRef.current.value;
console.log('Отправка:', { email, password });
};
return (
<form onSubmit={handleSubmit}>
{/* ref — ссылка на DOM элемент, нет value/onChange */}
<input type="email" ref={emailRef} defaultValue="" placeholder="Email" />
<input type="password" ref={passwordRef} placeholder="Пароль" />
<button type="submit">Войти</button>
</form>
);
}
// Файловый инпут — всегда uncontrolled
function FileUpload() {
const fileRef = useRef(null);
const handleUpload = () => {
const file = fileRef.current.files[0];
console.log('Файл:', file.name, file.size);
};
return (
<div>
{/* value нельзя установить программно для type="file" */}
<input type="file" ref={fileRef} />
<button onClick={handleUpload}>Загрузить</button>
</div>
);
}
Сравнение
| Controlled | Uncontrolled | |
|---|---|---|
| Источник истины | React state | DOM |
| Ре-рендер при вводе | Да | Нет |
| Валидация в реальном времени | Легко | Сложно |
| Доступ к значению | state.value |
ref.current.value |
| Файловый инпут | Нельзя | Да |
Частые ошибки
- Переход controlled → uncontrolled — если
valueменяется сundefinedна строку, React выбрасывает предупреждение; всегда инициализируйте state строкой. - Нет
onChangeприvalue— input сvalueбезonChangeстановится read-only; React предупредит об этом. - Использование
defaultValueс controlled —defaultValueдля uncontrolled,valueдля controlled; не смешивайте.