React – самый популярный инструмент для фронтенд-разработки. Подробно рассматриваем основные концепции и учимся применять их на практике.
В последнем большом обновлении React получил хуки – особый способ доступа к API библиотеки. Это позволяет отказаться от создания классовых компонентов, используя при этом все их замечательные возможности (состояние, обработка побочных эффектов). Благодаря хукам можно сохранять код приложения чище и логичнее. В этом справочнике мы сконцентрируемся именно на них – hooks-first подход в React-разработке.
Базовые концепции
Если вы только начинаете знакомиться с React, загляните в наши вводные материалы:
Простейший React-элемент выглядит как обычный HTML. Это возможно благодаря особому JSX-синтаксису (JavaScript XML):
JSX-элементы являются выражениями:
Внутри JSX также можно использовать выражения. Для их интерполяции используются фигурные скобки:
Таким образом, можно выводить любые примитивные значения и массивы, но попытка вставить сюда объект приведет к ошибке.
В один JSX-элемент можно вкладывать другие, как в обычные HTML-теги:
Особенности JSX
Несмотря на сходство с HTML, JSX все-таки им не является. Это расширение языка JavaScript, которое просто позволяет удобнее создавать DOM-элементы.
В JSX есть особенности. Например, все теги должны быть закрыты, а пустой тег можно закрыть сразу же:
Имеются различия в атрибутах. Например, вместо class нужно использовать className. Все имена атрибутов пишутся в camelCase, как JS-переменные.
Для создания простого React-проекта нужно всего три вещи:
DOM-элемент, внутри которого будет располагаться все приложение. В этой роли может выступать обычный div;
Корневой JSX-элемент, который вы поместите в этот div;
Метод ReactDOM.render(), который сделает всю работу по рендерингу за вас.
Компоненты и пропсы
Создание
В React есть два основных способа объявления компонента: функциональный и классовый. С первым все просто: компонент – это обычная функция, которая возвращает JSX-разметку.
Классы имеют чуть более сложный шаблон. Они должны наследовать от React.Component и реализовывать метод render:
Примечание: каким бы способом вы не пользовались, помните, что имя компонента обязательно должно начинаться с большой буквы.
Использование
Чтобы включить компонент в разметку, вам не нужно вызывать его (для функциональных компонентов) или его метод (для классовых). Название компонента используется как обычный HTML-тег, в угловых скобках:
Компоненты в приложении можно использовать многократно как обычные теги. Например, Header можно выводить на разных страницах:
Передача данных
Кроме того, в компоненты при объявлении можно динамически передавать данные. Например, в Header можно передать имя авторизованного пользователя:
Пропсы нельзя изменять (мутировать) внутри компонента, они предназначены только для чтения. В идеале React-компоненты должны быть "чистыми" функциями (pure functions). Входные данные таких функций не должны изменяться.
Если параметр должен изменяться внутри компонента, скорее всего, это не свойство (prop), а состояние (state). Мы разберемся с ним чуть позже, в разделе useState.
В компонент можно вложить один или несколько дочерних элементов/компонентов. Чтобы получить к ним доступ, нужно обратиться к свойству props.children.
Условный рендер
Чтобы вывести (или не вывести) компонент по какому-либо условию, в JSX нужно воспользоваться тернарным оператором. Обычный if-else здесь не подойдет, так как интерполировать в JSX можно только выражения, то есть конструкции, которые возвращают какой-либо результат.
Фрагменты
В JSX есть одно непреложное требование – из каждого выражения должен возвращаться только один родительский компонент/элемент. Поэтому, чтобы вернуть несколько элементов, расположенных на одном уровне, приходится оборачивать их в лишний div.
Фрагменты решают эту проблему. По сути, это DocumentFragment. При вставке в DOM он оставляет дочерние элементы, а сам растворяется, как будто его и не было.
Простейший синтаксис <></> – это удобное сокращение для компонента <React.Fragment>.
Списки и ключи
Мы не можем использовать цикл for, чтобы вывести сразу несколько элементов в JSX, так как он не возвращает значение. Но мы можем вместо этого воспользоваться итерирующими методами массивов!
Array.prototype.map поможет превратить массив данных в массив JSX-элементов, который легко можно интерполировать в разметку.
.map() можно использовать и для вставки набора React-компонентов:
Важно! Каждый элемент/компонент в массиве должен иметь уникальное свойство key (уникальное только в пределах этого массива). Ключ необходим, чтобы React мог отслеживать каждый элемент.
Если исходный массив имен изменится, придется перерендерить весь список. Но если у элементов будут ключи, то React внесет правки только туда, где это необходимо.
Примечание: не стоит использовать в качестве уникального ключа индекс элемента в массиве, так как он может меняться.
События и обработчики событий
Обработка событий в React немного отличается от HTML.
Чаще всего в React вы будете использовать события onClick и onChange.
onClick отслеживает клики по элементам, аналогично onclick в HTML;
onChange обрабатывает изменения полей ввода (как oninput).
Хуки React
Состояние компонента и useState
С помощью хука useState можно создать локальное состояние внутри функционального компонента. Раньше это было доступно только компонентам-классам.
Примечание: все хуки, рассматриваемые в этом разделе, можно отдельно импортировать из пакета react.
Обновление состояния
Разумеется, созданное состояние можно обновить, иначе не имело бы смысла его создавать. Для этого используются функции-сеттеры:
Чтобы обновить состояние, мы просто передаем в сеттер его новое значение.
Обратите внимание, в качестве обработчика события используется стрелочная функция.
Внутри одного компонента хук useState можно использовать несколько раз:
В useState в качестве состояния можно передать примитивное значение или даже объект.
Если обновление зависит от предыдущего состояния, то в сеттер можно передать не объект, а функцию. Первым параметром она получит текущий state, а вернуть должна новый.
Побочные эффекты и хук useEffect
Хук useEffect позволяет выполнять из функционального компонента действия, которые вызывают побочные эффекты, например, получение данных с сервера, установка слушателей событий или взаимодействие с DOM-деревом.
Этот хук принимает функцию обратного вызова (эффект-функцию), которая будет вызываться при каждом перерендере, включая первый рендер компонента. Она запускается после монтирования компонента в документ.
Чтобы избежать вызова эффект-функции после каждого рендера, можно передать в хук второй аргумент – пустой массив.
Что это за массив? Просто коллекция аргументов, при изменении которых должна отработать наша эффект-функция. Если массив пуст, то она сработает лишь единожды. Если мы положим туда, к примеру, colorIndex, то при каждом его изменении будет меняться фон страницы. Но если перерендер вызван чем-то другим, то коллбэк хука не будет вызван.
Хук useEffect также позволяет выполнить какие-то действия при изменении компонента. Здесь можно, например, отписаться от прослушки событий DOM, чтобы не тратить память.
Нужно просто вернуть из эффект-функции другую функцию, и React вызовет ее при перерендере или удалении компонента.
Fetching data with useEffect
Сам коллбэк не может быть асинхронным (async), поэтому различные асинхронные операции нужно обрабатывать прямо внутри него или вынести в отдельную функцию:
Производительность и хук useCallback
Хук useCallback используется для улучшения производительности компонентов за счет мемоизации функций обратного вызова.
При частом обновлении постоянное пересоздание обработчиков – это дорогое удовольствие. useCallback позволяет изменять их только в случае реальной необходимости – когда изменяются связанные с ними зависимости.
Мемоизация и хук useMemo
Хук useMemo очень похож на useCallback и также используется для повышения производительности. Разница заключается в том, что вместо функций-коллбэков useMemo запоминает результаты дорогостоящих вычислений.
Если некоторая операция при одних и тех же входных данных всегда возвращает один и тот же результат, его можно поставить в соответствие этим данным и запомнить. Таким образом, в следующий раз не придется производить само вычисление. Достаточно будет взять сохраненный результат, соответствующий входным данным.
Хук useMemo возвращает результат вычисления.
Рефы и хук useRef
Рефы – это специальные атрибуты, доступные всем React-компонентам. Они позволяют создать ссылку на компонент (или HTML-элемент) после того, как он появится в DOM.
Хук useRef – это простой способ создавать рифы внутри функциональных компонентов. Он возвращает значение, которое можно привязать к любому элементу, чтобы на него можно было ссылаться.
С помощью такой ссылки можно изменять свойства элемент или вызывать его общедоступные методы (например, focus() у поля ввода).
Продвинутые хук
Контекст и хук useContext
В React существует проблема передачи свойств целевым компонентам. Обычно мы поднимаем данные по дереву компонентов, чтобы хранить их в одном месте. Но затем их приходится спускать вниз по цепочке пропсов для вывода на страницу. Иногда несколько уровней компонентов просто передают вниз ненужные им данные. чтобы они достигли цели.
Чтобы избежать этого, можно воспользоваться React-концепцией контекста. Это общая область видимости для целого дерева компонентов.
С хуком useContext все становится еще проще:
Редьюсеры и хук useReducer
Редьюсеры (или редукторы) – это простые чистые (предсказуемые) функции, которые получают в качестве аргументов предыдущее состояние объекта и объект действия (action), а возвращают обновленное состояние. Другими словами, редьюсеры применяют к состоянию некоторое действие.
Редьюсеры – это мощный паттерн управления состоянием, который используется в популярной библиотеке Redux. В то время как локальное состояние компонента регулируется хуком useState, useReducer позволяет управлять данными всего приложения.
Этот хук можно использовать вместе с useContext, чтобы с легкостью передавать данные заинтересованным компонентам.
Вот маленький пример полноценной системы управления состоянием, основанной на связке useReducer + useContext.
В useReducer передается функция-редуктор и начальное состояние приложения. Он возвращает состояние и функцию dispatch для вызова действий. В dispatch нужно передать объект действия, который будет передан редуктору, тот в свою очередь изменит состояние приложения.
Создание пользовательских хуков
Хуки создаются для того, чтобы можно было разделять одинаковое поведение между разными компонентами. Они работают гораздо очевиднее, чем компоненты высшего порядка или рендер-пропсы, которые нам приходилось использовать раньше.
React позволяет создавать кастомные хуки – и это очень здорово!
Правила хуков
В React есть два базовых правила использования хуков, которые обязательно нужно соблюдать:
Хуки можно вызывать только из верхнего уровня вашего компонента. Не следует обращаться к ним из блоков условий, циклов или вложенных функций.
Хуки можно вызывать только из функциональных компонентов. Внутри обычных JS-функций или классовых компонентов их использовать не следует.
Что ещё почитать?
В React существует еще много концепций, изучение которых будет полезно для прокачивания мастерства. Мы разобрали только базовые. Обратитесь к документации React для более полного погружения в тему.
Чтобы полезная информация всегда была под рукой, скачайте PDF-шпаргалку по основным возможностям библиотеки.