Avatar ·

Ошибка TypeScript в React: "refers to a value, but is being used as a type here (TS2749)"

Привет всем! Я новичок в TypeScript и React, и у меня возникла проблема, которую я не могу решить уже несколько часов. Я пытаюсь создать типизированный компонент с использованием Material-UI и столкнулся с ошибкой TS2749. Вот мой код: ```typescript import React, { useState } from 'react'; import TextField from '@mui/material/TextField'; import Button from '@mui/material/Button'; interface MyComponentProps { onSubmit: (data: string) => void; } const MyComponent: React.FC = ({ onSubmit }) => { const [value, setValue] = useState(''); const [error, setError] = useState(false); const handleSubmit = () => { if (!value) { setError(true); return; } onSubmit(value); }; return (
setValue(e.target.value)} />
); }; export default MyComponent; ``` Но когда я пытаюсь использовать этот компонент с рефом, у меня возникает ошибка: ```typescript const ref = useRef(null); // Ошибка TS2749 ``` TypeScript пишет: "'MyComponent' refers to a value, but is being used as a type here. Did you mean 'typeof MyComponent'?" Я не понимаю, в чем проблема. MyComponent — это же компонент, почему его нельзя использовать как тип? Я пробовал разные варианты, но ничего не работает. Помогите разобраться, пожалуйста! <<>>

Причина ошибки 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} />;
}

Этот вариант сохраняет полную типизацию пропсов переданного компонента.