До React 19 функціональний компонент не отримував ref як prop - для цього обгортали компонент у forwardRef:
const Input = forwardRef<HTMLInputElement, InputProps>(function Input(props, ref) {
return <input ref={ref} {...props} />;
});
У React 19 ref - звичайний prop:
import type { ComponentProps } from 'react';
type InputProps = ComponentProps<'input'> & {
label: string;
};
function Input({ label, ref, ...props }: InputProps) {
return (
<label>
{label}
<input ref={ref} {...props} />
</label>
);
}
const emailRef = useRef<HTMLInputElement>(null);
<Input label="Email" type="email" ref={emailRef} required />;
forwardRef ще працює, але документація позначає його як непотрібний для нового коду, і в майбутніх версіях його оголосять застарілим.
ComponentProps<'input'> - усі атрибути нативного <input>, включно з ref, обробниками подій, aria-* і data-*. Обгортка автоматично приймає все, що приймає нативний елемент, без ручного перелічення.
Варіанти утилітних типів:
ComponentProps<'button'>- атрибути разом зref(у React 19 для нативних елементів);ComponentPropsWithoutRef<'button'>- те саме безref, коли обгортка свідомо не передає ref далі;ComponentProps<typeof Button>- props іншого компонента. Корисно, щоб розширити чужий компонент чи переиспользувати його типи без експорту.
Перевизначення атрибута: якщо власний prop збігається з нативним, але має інший тип, нативний треба прибрати:
type SelectProps = Omit<ComponentProps<'select'>, 'onChange'> & {
onChange: (value: string) => void;
};
Без Omit типи перетнуться, і TypeScript вимагатиме обробник, сумісний з обома сигнатурами одночасно.
Пастки:
- розгорнути
...propsдо свогоclassName- і переданий ззовні клас перезапише внутрішній. Порядок і злиття класів (clsx,tailwind-merge) треба продумати; - бібліотеки, що підтримують React 18, досі використовують
forwardRef, і це нормально - для них потрібна сумісність з обома версіями.