Ошибка TypeScript в React: "refers to a value, but is being used as a type here (TS2749)"
Причина ошибки TS2749
Ошибка TS2749 возникает из-за фундаментального различия между значениями (values) и типами (types) в TypeScript. В вашем коде MyComponent — это функциональный компонент, который является значением (экземпляром функции). Когда вы пишете useRef<MyComponent>, TypeScript ожидает тип, но получает значение.
Проблема в том, что MyComponent — это переменная, содержащая функцию. Чтобы использовать её как тип, нужно либо применить typeof, либо использовать правильный тип для рефа.
Правильное решение
Для рефа в React компоненте нужно использовать тип React.ComponentRef или InstanceType. Вот корректный код:
import React, { useRef } from 'react';
// Вариант 1: Используем ComponentRef (рекомендуется)
const ref = useRef<React.ComponentRef<typeof MyComponent>>(null);
// Вариант 2: Используем InstanceType (альтернатива)
const ref = useRef<InstanceType<typeof MyComponent>>(null);
// Вариант 3: Если компонент обернут в React.memo или forwardRef
const ref = useRef<React.ElementRef<typeof MyComponent>>(null);
Почему это работает?
- typeof MyComponent — получает тип самой функции-компонента (а не её экземпляр)
- React.ComponentRef — извлекает тип рефа, который возвращает компонент (если он использует forwardRef)
- InstanceType — получает тип экземпляра класса (для классовых компонентов) или тип возвращаемого значения (для функциональных)
Полный пример исправления
import React, { useState, useRef } from 'react';
import TextField from '@mui/material/TextField';
import Button from '@mui/material/Button';
interface MyComponentProps {
onSubmit: (data: string) => void;
}
const MyComponent: React.FC<MyComponentProps> = ({ onSubmit }) => {
const [value, setValue] = useState<string>('');
const [error, setError] = useState<boolean>(false);
const handleSubmit = () => {
if (!value) {
setError(true);
return;
}
onSubmit(value);
};
return (
<div>
<TextField
error={error}
label="Введите данные"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
<Button variant="contained" onClick={handleSubmit}>
Отправить
</Button>
</div>
);
};
// Исправленный код с рефом
const ParentComponent: React.FC = () => {
const ref = useRef<React.ComponentRef<typeof MyComponent>>(null);
return <MyComponent onSubmit={(data) => console.log(data)} />;
};
export default MyComponent;
Дополнительные советы
- Если ваш компонент использует
forwardRef, обязательно используйтеReact.ComponentRef - Для классовых компонентов можно использовать
InstanceTypeнапрямую - Избегайте использования
any— это убивает всю пользу от TypeScript
Альтернативный подход: использование React.ElementType
Если вам нужно передать сам компонент как пропс (например, для динамического рендеринга), можно использовать тип React.ElementType. Это полезно для создания гибких компонентов-обёрток.
import React from 'react';
interface WrapperProps {
component: React.ElementType;
// другие пропсы
}
const Wrapper: React.FC<WrapperProps> = ({ component: Component, ...props }) => {
return <Component {...props} />;
};
// Использование
const App = () => {
return <Wrapper component={MyComponent} onSubmit={(data) => console.log(data)} />;
};
Когда это полезно?
- Создание HOC (Higher-Order Components)
- Динамический выбор компонента на основе условий
- Библиотеки UI, где нужно передавать кастомные компоненты
Этот подход решает проблему TS2749, потому что React.ElementType — это тип, который принимает значение компонента. Однако помните, что при таком подходе вы теряете строгую типизацию пропсов, поэтому для production-кода лучше использовать дженерики.
// Более строгий вариант с дженериками
function Wrapper<T extends React.ElementType>({
component: Component,
...props
}: {
component: T;
} & React.ComponentPropsWithoutRef<T>) {
return <Component {...props} />;
}
Этот вариант сохраняет полную типизацию пропсов переданного компонента.
Ответов пока нет.
Войдите чтобы оставить ответ