Что такое controlled и uncontrolled компоненты в React?
Что значит controlled и uncontrolled
В React элементы форм (input, textarea, select) могут работать в двух режимах — controlled (управляемый) и uncontrolled (неуправляемый). Разница в том, где находится «источник правды» (source of truth) для текущего значения поля.
- Controlled — значение хранится в state компонента. Атрибут value элемента всегда равен значению из state, а изменения обрабатываются через onChange, который обновляет этот state.
- Uncontrolled — значение хранится в самом DOM-узле, React не следит за ним напрямую. Чтобы прочитать значение, используется ref.
Controlled-компонент
function LoginForm() {
// состояние хранит актуальное значение поля
const [email, setEmail] = useState('');
return (
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
);
}
Каждое нажатие клавиши вызывает onChange, обновляет state, и React перерисовывает input с новым value. Значение полностью контролируется React: если не обновить state, содержимое поля не изменится, даже если пользователь печатает.
Uncontrolled-компонент
function LoginForm() {
const emailRef = useRef<HTMLInputElement>(null);
const handleSubmit = () => {
// читаем значение напрямую из DOM в момент отправки
console.log(emailRef.current?.value);
};
return (
<input type="email" ref={emailRef} defaultValue="" />
);
}
Здесь React не хранит значение поля в state — оно живёт в DOM. defaultValue задаёт только начальное значение, а дальше браузер сам управляет содержимым инпута. Значение достаётся через ref только тогда, когда оно действительно нужно, например при сабмите формы.
Основные отличия
- Controlled: value + onChange, uncontrolled: defaultValue/defaultChecked + ref.
- Controlled даёт мгновенный доступ к значению на каждом рендере, uncontrolled — только по запросу через ref.
- Controlled проще для валидации на лету, масок ввода, disabled-логики кнопок.
- Uncontrolled требует меньше кода и меньше ре-рендеров, но хуже поддаётся программному управлению.
- Для чекбоксов и радио аналогично: checked/onChange — controlled, defaultChecked — uncontrolled.
- input type="file" всегда uncontrolled: значение файла нельзя задать программно из соображений безопасности, доступ к файлам возможен только через ref.
Когда что использовать
Controlled предпочтителен, когда нужна валидация «на лету», условное отключение кнопки отправки, форматирование ввода (маски) или синхронизация нескольких полей между собой. Uncontrolled уместен там, где важен минимальный boilerplate: простые формы без сложной логики, интеграция со сторонними DOM-библиотеками, а также для input type="file", который в принципе не может быть controlled.
В реальных проектах часто смешивают подходы: сложные поля делают controlled, а простые вспомогательные — uncontrolled, если их значение нужно только один раз, например при сабмите.
Что хочет услышать интервьюер
Понимание, что controlled-компонент хранит значение в state и вызывает re-render при каждом изменении
Знание, что uncontrolled-компонент хранит значение в DOM, а доступ к нему получают через ref
Умение написать пример кода для обоих подходов
Понимание разницы value/onChange vs defaultValue/defaultChecked
Знание, что input type="file" всегда uncontrolled
Пример: Controlled input
function LoginForm() {
const [email, setEmail] = useState('');
return (
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
);
}
Пример: Uncontrolled input
function LoginForm() {
const emailRef = useRef<HTMLInputElement>(null);
const handleSubmit = () => {
console.log(emailRef.current?.value);
};
return <input type="email" ref={emailRef} defaultValue="" />;
}
Типичные ошибки
Указывают value без onChange, из-за чего React выводит предупреждение и поле становится read-only
Одновременно передают value и defaultValue одному элементу
Путают ref с state и пытаются читать значение uncontrolled-поля на каждом рендере
Считают uncontrolled-подход изначально плохой практикой, не понимая случаев его уместного использования
Забывают, что чекбоксы и радио используют checked/defaultChecked, а не value/defaultValue


