Урок 03

Неконтролируемые компоненты

Неконтролируемый компонент хранит значение в DOM, а не в React. Доступ к значению — через ref.

Базовый пример

import { useRef } from 'react';

function Form() {
    const inputRef = useRef<HTMLInputElement>(null);

    function handleSubmit(e: React.FormEvent) {
        e.preventDefault();
        console.log(inputRef.current?.value);
    }

    return (
        <form onSubmit={handleSubmit}>
            <input ref={inputRef} defaultValue="начальное" />
            <button>Отправить</button>
        </form>
    );
}

defaultValue задаёт начальное значение, ref даёт доступ к DOM.

Когда использовать

  • Простые формы без валидации на лету
  • Интеграция со сторонними библиотеками (например, редакторами)
  • Загрузка файлов
  • Когда нужна производительность (не каждый ввод вызывает ререндер)

Файлы

Неконтролируемый input — стандарт для файлов:

function Upload() {
    const fileRef = useRef<HTMLInputElement>(null);

    function handleSubmit(e: React.FormEvent) {
        e.preventDefault();
        const file = fileRef.current?.files?.[0];

        if (!file) return;

        const formData = new FormData();
        formData.append('file', file);

        fetch('/api/upload', {
            method: 'POST',
            body: formData,
        });
    }

    return (
        <form onSubmit={handleSubmit}>
            <input type="file" ref={fileRef} />
            <button>Загрузить</button>
        </form>
    );
}

Поле type="file" всегда неконтролируемое.

Контролируемый vs неконтролируемый

КонтролируемыйНеконтролируемый
Значение в ReactЗначение в DOM
Ре-рендер на каждый вводБез ререндера
Валидация на летуВалидация при отправке
Программное изменениеЧерез ref
Больше кодаМеньше кода

Пример: контролируемый

const [email, setEmail] = useState('');

<input value={email} onChange={(e) => setEmail(e.target.value)} />

Пример: неконтролируемый

const emailRef = useRef<HTMLInputElement>(null);

<input ref={emailRef} defaultValue="" />

Форма целиком

Иногда удобно работать с формой как с неконтролируемой:

function Form() {
    const formRef = useRef<HTMLFormElement>(null);

    function handleSubmit(e: React.FormEvent) {
        e.preventDefault();

        const data = new FormData(formRef.current!);
        const email = data.get('email');
        const password = data.get('password');

        console.log({ email, password });
    }

    return (
        <form ref={formRef} onSubmit={handleSubmit}>
            <input name="email" type="email" />
            <input name="password" type="password" />
            <button>Войти</button>
        </form>
    );
}

FormData собирает все поля по имени name.

Смешанный подход

Можно смешивать: часть полей контролируемые, часть — нет.

const [email, setEmail] = useState('');
const passwordRef = useRef<HTMLInputElement>(null);

<input value={email} onChange={(e) => setEmail(e.target.value)} />
<input ref={passwordRef} type="password" />

Но обычно лучше держать единый стиль.

Reset формы

Неконтролируемую форму легко сбросить:

formRef.current?.reset();

Для контролируемой нужно обнулять каждое поле.

Итоги

  • Неконтролируемый — значение в DOM
  • Доступ через ref
  • defaultValue для начального значения
  • Файлы — всегда неконтролируемые
  • FormData собирает всю форму
  • Меньше кода, но меньше контроля