Если открыть старый React-код, там почти наверняка будут class-компоненты. Если открыть новый туториал, там почти наверняка будут hooks. Поэтому вопрос обычно звучит не как «что такое hooks», а как «что мне делать со своим кодом прямо сейчас».

Короткий ответ такой: для нового React-кода hooks давно стали базовым выбором, но class-компоненты по-прежнему важно понимать, если ты работаешь с легаси-кодом, миграциями или boundary-слоями вокруг ошибок.

Если убрать идеологию, решение обычно сводится к трем правилам: новый код пиши на hooks, старый код не переписывай без повода, а class-компоненты держи в голове как часть реального React-наследия, а не как тему “из прошлого”.

Какой из них использовать

Официальная позиция команды React (согласно документации):

When you’re ready, we’d encourage you to start trying Hooks in new components you write. […] We don’t recommend rewriting your existing classes to Hooks unless you planned to rewrite them anyway (e.g. to fix bugs).

Итог:

  • новый код должен использовать функциональные компоненты с хуками;
  • старый код может продолжать использовать компоненты класса, если ты не хочешь переписывать.

Должен ли я тогда сосредоточиться на хуках?

Это не так просто.

Тебе по-прежнему нужны class-компоненты для Error Boundaries. Можно найти библиотеку на основе хуков, но незнание механизма не оправдывает лишнюю зависимость.

Вдобавок ко всему, большая часть кода, написанного до 2019 года, скорее всего, по-прежнему будет использовать компоненты класса, поскольку нет необходимости немедленно переписывать их в функциональные компоненты с помощью хуков. Если ты хочешь понять существующий код, тебе будет необходимо изучить компоненты класса.

Также ты обнаружишь, что компании, которые задают вопросы по React во время собеседований, по-прежнему будут спрашивать о классах.

Нужно ли переписать старый код на основе классов для использования хуков

Как и во всем хорошем, здесь есть компромиссы.

Хуки приводят к гораздо более чистым и легким для понимания компонентам по сравнению с компонентами классов аналогичной сложности.

Для сравнения: один и тот же компонент, который запрашивает данные из Star Wars API, — сначала как класс, затем как функциональный с хуками:

import React from 'react';

export default class DataDisplayer extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      data: null,
    };
  }
  async componentDidMount() {
    const response = await fetch(
      `https://swapi.dev/api/people/${this.props.id}/`
    );
    const newData = await response.json();
    this.setState({ data: newData });
  }
  render() {
    const { data } = this.state;
    if (data) {
      return <div>{data.name}</div>;
    } else {
      return null;
    }
  }
}

По мере роста приложения методы жизненного цикла становятся больше, и увеличивается переключение контекста, связанное только с прокруткой файла.

Не знаю, как у тебя, но мой мыслительный процесс при просмотре классов такой:

  • хорошо, я нахожусь в componentDidMount, поэтому буду получать данные здесь;

  • выполняем рендеринг, поэтому этот код запускается каждый раз;

  • нужно добавить дополнительную функциональность … хм, какой метод жизненного цикла снова используется?.

С другой стороны, есть хуки:

import React, { useEffect, useState } from 'react';

export default function DataDisplayer(props) {
  const [data, setData] = useState('');

  useEffect(() => {
    const getData = async () => {
      const response = await fetch(`https://swapi.dev/api/people/${props.id}/`);
      const newData = await response.json();
      setData(newData);
    };
    getData();
  }, [props.id]);

  if (data) {
    return <div>{data.name}</div>;
  } else {
    return null;
  }
}

С помощью хуков написание кода намного проще, и я считаю, что чтение функциональных компонентов с помощью хуков требует меньшего переключения контекста, поскольку ты не прыгаешь по файлу, чтобы определить, в каком методе жизненного цикла, по твоему мнению, что-то произошло.

Это главное преимущество переписывания: понять, что делает компонент, становится быстрее.

Главный недостаток - время; время, потраченное на переписывание, - это время, которое ты мог бы потратить на создание новых функций или написание интеграционных тестов.

Что дальше делать

Я рекомендую следующий подход, который обычно дает лучший баланс:

  • весь новый код писать как функциональные компоненты с hooks;
  • существующий код переписывать только тогда, когда это совпадает с задачей: исправление ошибки, расширение логики, упрощение тестов;
  • class-компоненты не демонизировать, а понимать их настолько, чтобы спокойно поддерживать легаси и boundary-слои.

Итог

Если тебе нужен практичный ориентир, он простой: новый код пиши на hooks, старый переписывай только там, где это окупается вместе с задачей. Такой подход обычно дает лучший баланс между качеством кода и стоимостью изменений.

Самая дорогая ошибка здесь - переписывать классы просто потому, что они “старые”. Самая полезная привычка - каждый раз спрашивать, улучшит ли миграция читаемость, тестируемость и скорость дальнейших изменений именно в этом месте кода.