← Назад к вопросам

Где нужно использовать классы в React?

2.0 Middle🔥 301 комментариев
#React#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Использование классов в React

В современном React предпочтение отдаётся функциональным компонентам с хуками. Однако класс-компоненты все ещё имеют место в определённых сценариях.

Почему функциональные компоненты - приоритет

В современной разработке функциональные компоненты с хуками - это стандарт. Они:

  • Проще писать и понимать
  • Имеют меньше boilerplate кода
  • Легче тестировать
  • Лучше с точки зрения производительности
  • Поддерживаются новыми возможностями React (Suspense, Concurrent features)
// Современный подход - функциональный компонент
function UserProfile({ userId }: { userId: string }) {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetchUser(userId).then(setUser).finally(() => setLoading(false));
  }, [userId]);

  if (loading) return <p>Загрузка...</p>;
  return <div>{user?.name}</div>;
}

Сценарии использования класс-компонентов

1. Error Boundaries (обработка ошибок)

Error Boundaries можно создавать только на основе классов. Это единственный официальный способ обработки ошибок в React:

class ErrorBoundary extends React.Component<
  { children: React.ReactNode },
  { hasError: boolean; error: Error | null }
> {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error: Error) {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    console.error('Error caught:', error, errorInfo);
    // Логируем ошибку на сервис мониторинга (Sentry, etc.)
  }

  render() {
    if (this.state.hasError) {
      return (
        <div>
          <h1>Что-то пошло не так</h1>
          <p>{this.state.error?.message}</p>
        </div>
      );
    }
    return this.props.children;
  }
}

// Использование
<ErrorBoundary>
  <App />
</ErrorBoundary>

Для функциональных компонентов можно использовать библиотеки вроде react-error-boundary:

import { ErrorBoundary } from 'react-error-boundary'

function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    <div>
      <h1>Что-то пошло не так</h1>
      <p>{error.message}</p>
      <button onClick={resetErrorBoundary}>Попробовать снова</button>
    </div>
  )
}

<ErrorBoundary FallbackComponent={ErrorFallback}>
  <App />
</ErrorBoundary>

2. Legacy код и миграция

Если работаете с существующим проектом, где используются класс-компоненты, нет смысла немедленно всё переписывать:

// Старый код, который работает - оставьте как есть
class LegacyComponent extends React.Component {
  state = { count: 0 };

  render() {
    return (
      <button onClick={() => this.setState({ count: this.state.count + 1 })}>
        Count: {this.state.count}
      </button>
    );
  }
}

// Новые компоненты пишите функциональными
function ModernComponent() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}

3. Нишевые случаи с жизненными циклами

Для очень специфичных сценариев, где нужна полная власть над жизненным циклом:

class DataFetcher extends React.Component<{
  url: string
  onData: (data: any) => void
}> {
  componentDidMount() {
    this.fetchData();
  }

  componentDidUpdate(prevProps) {
    if (prevProps.url !== this.props.url) {
      this.fetchData();
    }
  }

  componentWillUnmount() {
    this.abortController?.abort();
  }

  private abortController = new AbortController();

  async fetchData() {
    try {
      const response = await fetch(this.props.url, {
        signal: this.abortController.signal
      });
      const data = await response.json();
      this.props.onData(data);
    } catch (error) {
      console.error(error);
    }
  }

  render() {
    return <p>Загрузка...</p>;
  }
}

Но то же самое легче с хуками:

function DataFetcher({ url, onData }: { url: string; onData: (data: any) => void }) {
  useEffect(() => {
    const controller = new AbortController();

    fetch(url, { signal: controller.signal })
      .then(res => res.json())
      .then(onData)
      .catch(console.error);

    return () => controller.abort();
  }, [url, onData]);

  return <p>Загрузка...</p>;
}

Сравнение: Класс vs Функциональный компонент

// Класс-компонент
class TodoList extends React.Component<{ items: string[] }> {
  state = { newItem: ' };

  handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    this.setState({ newItem: e.target.value });
  };

  handleAddTodo = () => {
    console.log('Added:', this.state.newItem);
    this.setState({ newItem: ' });
  };

  render() {
    return (
      <div>
        <ul>
          {this.props.items.map((item, i) => (
            <li key={i}>{item}</li>
          ))}
        </ul>
        <input value={this.state.newItem} onChange={this.handleChange} />
        <button onClick={this.handleAddTodo}>Добавить</button>
      </div>
    );
  }
}

// Функциональный компонент (современный подход)
function TodoList({ items }: { items: string[] }) {
  const [newItem, setNewItem] = useState(')

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setNewItem(e.target.value);
  };

  const handleAddTodo = () => {
    console.log('Added:', newItem);
    setNewItem(');
  };

  return (
    <div>
      <ul>
        {items.map((item, i) => (
          <li key={i}>{item}</li>
        ))}
      </ul>
      <input value={newItem} onChange={handleChange} />
      <button onClick={handleAddTodo}>Добавить</button>
    </div>
  );
}

Когда НЕ использовать классы

// НЕПРАВИЛЬНО - класс для простого компонента
class Welcome extends React.Component {
  render() {
    return <h1>Привет, {this.props.name}!</h1>;
  }
}

// ПРАВИЛЬНО - функциональный компонент
function Welcome({ name }: { name: string }) {
  return <h1>Привет, {name}!</h1>;
}
// НЕПРАВИЛЬНО - класс для управления состоянием
class Counter extends React.Component {
  state = { count: 0 };

  render() {
    return (
      <button onClick={() => this.setState({ count: this.state.count + 1 })}>
        Count: {this.state.count}
      </button>
    );
  }
}

// ПРАВИЛЬНО - функция с хуком
function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}

Общая стратегия

Правило 95:5:

  • 95% компонентов должны быть функциональными с хуками
  • 5% случаев - это Error Boundaries и legacy код
// Структура современного React приложения
export function App() {
  return (
    <ErrorBoundary>  {/* Класс-компонент для обработки ошибок */}
      <Layout>
        <HomePage />          {/* Функциональный компонент */}
        <UserProfile />       {/* Функциональный компонент */}
        <Settings />          {/* Функциональный компонент */}
      </Layout>
    </ErrorBoundary>
  );
}

Итог

  1. Используйте функциональные компоненты с хуками - это современный стандарт
  2. Используйте классы только для Error Boundaries - это единственный встроенный способ
  3. Для legacy кода - миграция постепенная, не спешите
  4. Не смешивайте стили - либо классы, либо функции, но не вместе
  5. При необходимости обработки ошибок - используйте react-error-boundary для функциональных компонентов

Современный React - это фактически React функциональных компонентов и хуков.

Где нужно использовать классы в React? | PrepBro