RU

React.js

React.js

React

React и чистый JavaScript:

  • Код на React более читаем (JSX = JS + HTML в одном файле)
  • Компоненты
  • JS императивный (буквально даёшь пошаговые инструкции)
  • React декларативный (описываешь, что именно хочешь получить)
// Императивный подход
const element = document.createElement("p");
element.textContent = "example";
element.className = "styles";
element.addEventListener("click", function () {});
// Декларативный
<p className="styles" onClick={function () {}}>
  example
</p>;

Виртуальный DOM

  • Виртуальный DOM представляет собой упрощённые объекты JavaScript по сравнению с настоящим DOM.

Сверка (Reconciliation)

  • Это процесс сравнения старой и новой версий (снимков) виртуального DOM после изменения данных (например, массива, на основе которого отображается список).
  • На основе этой сверки React вносит точечные изменения в реальный DOM.
  • В итоге пользователь видит обновление только изменённого элемента списка, а не полную перерисовку.

Базовые хуки

useState

  • Локальное состояние
  • Сравнивает предыдущие значения с новыми и при необходимости вызывает повторный рендер тех частей UI, которые от него зависят (в отличие от обычной переменной, которую мы вывели бы в JSX и изменили в обработчике события)
  • правильное изменение состояния setState(prev => prev + 1)
  • useState(() => compute()) — можно передать функцию в качестве начального значения, чтобы вычислить его один раз, а не при каждом рендере

useRef

  • Можно использовать как useState, но изменение не вызывает повторный рендер
  • Также может хранить ссылку на DOM-элемент

useContext

  • Нужен для избежания props drilling, когда приходится пробрасывать состояние через пропсы каждого уровня, даже там, где оно не нужно
  • Подходит для небольшого глобального состояния; для сложных сценариев используют менеджеры состояния (Redux, Zustand, React Query и т.д.)

useCallback

  1. Используется вместе с React.memo для функций, передаваемых в дочерний компонент, чтобы между рендерами родителя сохранялась та же ссылка.

useMemo

Сценарии использования:

  1. Аналогично useCallback, в дополнение к React.memo, но для объектов, массивов и т.д.
  2. Когда одно из состояний сложно вычисляется, а мы изменяем другое состояние, при ререндере сложное состояние пересчитывается снова.
function formatData() {
  return data.map((item) => {
    // сложные вычисления (форматирование, сортировка, фильтрация)
  });
}
const formattedData = useMemo(formatData, [data]);

Оптимизация

Атрибут key

  • Нужен для оптимизации обновления списка элементов, созданного с помощью метода map.
  • Если мы добавляем новый элемент в начало или середину массива, React с key сможет корректно сопоставить существующие элементы и обновить только изменённые, избегая полной перерисовки всех элементов.
  • Не рекомендуется использовать индекс массива, если список динамичный (удаление элемента из середины массива, будет рассмотрено Реактом так, будто удалился последний элемент массив, а все остальные элементы после реально удаленного изменились)

React.lazy

  • Нужен, когда не хотим, чтобы компонент попадал в начальный бандл (собранный файл из всех файлов проекта).
  • Начальная загрузка будет быстрее (бандл меньше), но пользователь будет ждать, когда компонент подгрузится.
  • Такой компонент следует оборачивать в React.Suspense и указывать загрузчик в fallback (бандл делится на чанки).

Profiler

  • Компонент для измерения времени рендеринга
  • Оборачиваем часть дерева в <Profiler id="name" onRender={callback}>
  • Подробный анализ доступен во вкладке Profiler в React DevTools

Оптимизация повторных рендеров дочерних компонентов

  • Без PureComponent / shouldComponentUpdate / React.memo компоненты перерисовываются каждый раз при ререндере родителя независимо от изменения пропсов.
// UserName будет перерисовываться каждый раз при клике на кнопку
<>
  <button onClick={() => setState((prev) => prev + 1)}>Add</button>
  <UserName name="John Doe" />
</>

PureComponent

  • При наследовании от PureComponent сравниваются новые и старые props и state, предотвращая ререндер, если они одинаковые.

shouldComponentUpdate(nextProps, nextState)

  • Делает то же, но в этом методе жизненного цикла можно реализовать пользовательскую логику сравнения props и state.

HOC React.memo

  • Делает то же для функциональных компонентов и только для props.
  • Предотвращает ререндер, когда props не изменились.
// не будет перерисовываться при рендере родителя, только при изменении props.name
const UserName = React.memo((props) => {
  return <div>{props.name}</div>;
});
  • !!! React.memo / PureComponent делают только поверхностную проверку (примитивы и ссылки).
  • Когда в пропс передан не примитивный тип, дополнительно к React.memo данные нужно обернуть в useMemo / useCallback.
  • Использование этих хуков сохраняет ссылки на функции и объекты между рендерами, предотвращая лишние перерисовки.
const user = useMemo(() => ({ name: "John Doe" }), []);
const handleClick = useCallback(() => setState((prev) => prev + 1), []);
return (
  <>
    <UserName user={user} />
    <button onClick={handleClick}>Add</button>
  </>
);

Продвинутые хуки

useLayoutEffect

  • Применяется, когда нужно напрямую изменить стили в DOM-дереве (ref.current.style.border = "1px solid black")
  • и хотим, чтобы они применились до отрисовки — синхронно, а не после рендера, как в обычном useEffect.

useImperativeHandle

  • Нужен, когда из родительского компонента, например, нужно очистить поля, привязанные к локальному состоянию дочернего.
  • Через forwardRef в дочернем компоненте раскрываются методы, которые доступны только внутри него, а в родительском их можно вызвать через ссылку.

useTransition

  • Используем, когда тяжёлый ререндеринг влияет на отзывчивость страницы.
  • С его помощью можно приоритизировать состояния, влияющие на отзывчивость (текстовый ввод, переключение табов, таймлайн).
  • И понизить приоритет состояния, блокирующего интерфейс (рендер результатов фильтрации списка по вводу, переключение содержимого таба).
const [input, setInput] = useState("");
const [list, setList] = useState([]);
const [isPending, startTransition] = useTransition();

function handleChange(e) {
  setInput(e.target.value); // устанавливаем приоритетное состояние
  startTransition(() => {
    const filteredValue = data.filter((item) => item); // тяжёлые вычисления фильтрации
    setList(filteredValue); // состояние с меньшим приоритетом внутри startTransition
  });
}

Чего нет в React из коробки

  • Маршрутизация (React Router, Next.js)
  • Глобальное состояние (Redux, MobX, Zustand)
  • Data fetching и кеширование (fetch/axios, React Query, SWR)
  • Управление формами (React Hook Form, Formik)
  • i18n и локализация (react-i18next)

Техники в React

Порталы

  • Позволяют рендерить дочерний компонент вне иерархии родителя в DOM.
  • Используется, например, для модальных окон, тултипов и всплывающих меню.
  • Работает через ReactDOM.createPortal(children, container).
const portalTargetElement = document.getElementById("portal-root")
return ReactDOM.createPortal(
    <div className="modal">Hello!</div>, portalTargetElement
);

Error Boundary

  • Реализуются только на классовых компонентах (componentDidCatch и getDerivedStateFromError).

Важные библиотеки, связанные с React

React-router-dom V6

  • Основные компоненты и хуки: и useNavigate, useParams, useLocation
  • Поддержка динамических маршрутов (/user/:id).
  • В версиях 6.4+ роутер поддерживает загрузку данных через loader и отправку через action

Redux

  • Redux хранит состояние объектов в едином store
  • Чтобы изменить состояние, нужно отправить action; action попадает в reducer, reducer описывает, как изменится state
  • Изменения происходят только с помощью чистой функции reducer — создаются копии и подменяются
  • ACTION — простой объект { type, payload }
  • Методы dispatch(action), getState(), subscribe(listener) принадлежат store (store.dispatch)
  • mstp выбирает нужную для компонента часть state и отслеживает изменения в выбранных свойствах
  • FLOW — dispatch(action) -> reducer(current state, action) -> возвращается новый экземпляр state

Redux-toolkit

  • Не используем ActionCreators
  • Используем ThunkCreator.fulfilled/rejected для успешных/неуспешных случаев санок
  • Настраиваем санки не на диспатч нужного AC, а на возврат данных/rejectedWithValue(error)

Redux-thunk

  • Middleware для Redux, позволяющий диспатчить асинхронные функции (thunks)
  • Thunk это частный случай HOF (отложенное вычисление)
  • Диспатч может принимать не только объект action, но и функцию (dispatch, getState) => {}.
  • Через это можно делать асинхронные запросы (fetch/axios), а результат уже диспатчить как обычный action.

React-query

  • Позволяет кэшировать запросы на уровне клиента.
  • Автоматически повторяет запросы при ошибке или восстановлении сети.
  • Снимает необходимость вручную писать loading/error/success стейты.

MobX

  • Менеджер состояния на основе observable значений
  • Компоненты автоматически реагируют на изменения; меньше шаблонного кода по сравнению с Redux
  • ООП стиль (observable, классы для сторов, мутабельный стейт)