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
- Используется вместе с React.memo для функций, передаваемых в дочерний компонент, чтобы между рендерами родителя сохранялась та же ссылка.
useMemo
Сценарии использования:
- Аналогично useCallback, в дополнение к React.memo, но для объектов, массивов и т.д.
- Когда одно из состояний сложно вычисляется, а мы изменяем другое состояние, при ререндере сложное состояние пересчитывается снова.
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, классы для сторов, мутабельный стейт)