Введение — JS: React Hooks
Хуки — механизм в React, который позволяет работать полностью без классов. Он не приносит ничего нового, но облегчает повторное использование кода для решения общих задач. Сейчас это основной способ написания React-приложений. Но хуки не заменяют собой классы целиком. Более того, команда React не планирует удалять поддержку классов, а кое-что без классов сделать не получится. Пример работы хука useState для хранения состояния:
// useState — встроенный в React хук // Подробнее рассматривается в следующем уроке import React, useState > from 'react'; const Example = () => // Пример хука для работы с состоянием const [count, setCount] = useState(0); return ( div> p>Вы нажали count> раз(а)p> button onClick=() => setCount(count + 1)>> Нажми меня button> div> ); >;
Хуки — это функции, имена которых принято начинать с use. Так их легко отличить от любых других функций. В React встроено около 10 хуков, из которых только несколько используются постоянно. К основным относятся те, что повторяют функциональность таких классовых компонентов, как работа с состоянием, побочными эффектами (жизненный цикл), контекстом и прямым доступом к DOM. Их мы рассмотрим в курсе. Информацию по остальным хукам можно найти в официальной документации.
Помимо встроенных, в сети можно найти сотни, если уже не тысячи готовых хуков на все случаи жизни. Например, популярная библиотека react-use насчитывает больше 115 хуков. Сейчас разработка на React во многом превратилась в поиск и использование подходящих хуков. Это хорошо, потому что можно сосредоточиться на действительно важных бизнес-задачах и не заниматься изобретением велосипедов.
Как подготовиться к этому курсу
Этот курс предполагает, что студент уже уверенно владеет синтаксисом и методами работы с функциями и объектами, а также имеет базовое представление о React. Если вам нужно освоить или освежить какие-то знания, рекомендуем вам следующие курсы:
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
- 130 курсов, 2000+ часов теории
- 1000 практических заданий в браузере
- 360 000 студентов
Наши выпускники работают в компаниях:
Введение в хуки
Хуки — нововведение в React 16.8, которое позволяет использовать состояние и другие возможности React без написания классов.
import React, useState > from 'react'; function Example() // Объявление переменной состояния, которую мы назовём "count" const [count, setCount] = useState(0); return ( div> p>Вы кликнули count> разp> button onClick=() => setCount(count + 1)>> Нажми на меня button> div> ); >
Первый хук, который мы изучим, это функция useState . Не беспокойтесь, если этот пример будет поначалу неясен. Скоро мы разберёмся, как он работает.
Вы можете начать изучать хуки на следующей странице. Здесь же мы расскажем, зачем мы добавили хуки в React, и как они помогут вам писать приложения.
Примечание
React 16.8.0 это первый релиз, поддерживающий хуки. При обновлении версии, не забудьте обновить и все зависимости, включая React DOM. Поддержка хуков в React Native появилась в версии 0.59.
На конференции React Conf 2018, Софи Алперт (Sophie Alpert) и Дэн Абрамов (Dan Abramov) представили хуки, а Райн Флоренс (Ryan Florence) показал, как их использовать в приложении. Видео конференции можно посмотреть здесь:
Полная обратная совместимость
Перед тем, как мы продолжим, обратите внимание, что хуки:
- Полностью на ваше усмотрение. Вы можете попробовать хуки в одних компонентах, не изменяя код в других. Хуки не обязательно использовать или изучать прямо сейчас.
- 100% обратно совместимы. Хуки не содержат изменений, которые могут поломать ваш существующий код.
- Доступны прямо сейчас. Хуки доступны с выходом версии 16.8.0.
Мы не планируем удалять классы из React. Вы можете прочитать больше о стратегии постепенного внедрения хуков в разделе ниже.
Хуки не меняют ваши знания о концепциях в React. Вместо этого, хуки предоставляют более прямой доступ к API уже знакомых вам понятий: пропсов, состояния, контекста, рефов, и жизненного цикла. Мы также рассмотрим мощный способ компоновать эти понятия с помощью хуков.
Чтобы начать изучать хуки, перейдите на следующую страницу! На этой странице мы расскажем о том, зачем нужны хуки, и как их использовать, не переписывая наши приложения.
Хуки решают множество, казалось бы, несвязанных между собой, проблем в React, с которыми мы сталкивались в течение пяти лет написания и поддержки десятков тысяч компонентов. Если вы изучаете React, используете его ежедневно или используете другую библиотеку с похожим компонентным подходом, эти проблемы наверняка покажутся вам знакомыми.
Трудно повторно использовать логику состояний между компонентами
В React нет способа «присоединить» повторно используемое поведение к компоненту (например, подключение к хранилищу). Если вы работали с React какое-то время, то вам могут быть знакомы такие паттерны, как рендер-пропсы и компоненты высшего порядка, которые пытаются решить эту проблему. Но эти паттерны заставляют вас изменять структуру компонентов, что делает код громоздким и трудным в поддержке. Если вы посмотрите на типичное React-приложение в React DevTools, то увидите «ад обёрток» из компонентов, окружённых провайдерами, консьюмерами, компонентами высшего порядка, рендер-пропсами и другими абстракциями. Хоть мы и можем отфильтровать их в DevTools, всё это указывает на более глубокую проблему в React. Нужен более удобный способ повторно использовать логику вокруг состояния.
С помощью хуков вы можете извлечь логику состояния из компонента, чтобы её протестировать или повторно использовать. Хуки позволяют вам повторно использовать логику состояния, не затрагивая дерево компонентов. Благодаря этому, хуки легко использовать в разных компонентах и делиться ими с сообществом.
Мы обсудим это подробнее в разделе Создание собственных хуков.
Сложные компоненты становятся трудными для понимания
Нам часто приходилось поддерживать компоненты, которые изначально были простыми, но превратились в неуправляемый беспорядок, состоящий из логики состояния и побочных эффектов. Каждый метод жизненного цикла часто содержит смесь несвязанной логики. Например, компоненты могут загружать данные в componentDidMount и componentDidUpdate . Однако тот же метод componentDidMount может содержать несвязанную логику, которая добавляет обработчики события с отменой подписки в componentWillUnmount . Взаимосвязанный код, который изменяется вместе, разделяется, но совершенно несвязанный код в конечном итоге объединяется в один метод. Это легко приводит к багам и несоответствиям в приложении.
В некоторых случаях невозможно разбить компоненты на более мелкие, потому что логика состояния раскидана повсюду. Такие компоненты сложно тестировать. Это одна из причин, по которой люди предпочитают использовать в React отдельную библиотеку для управления состоянием. Однако, это добавляет множество абстракций, заставляет прыгать между разными файлами и усложняет повторное использование компонентов.
Чтобы решить эту проблему, хуки позволяют разбить один компонент на маленькие функции по их назначению (например, подписке или загрузке данных), а не на основе методов жизненного цикла. Вы также можете контролировать внутреннее состояние с помощью редюсера, чтобы поведение было более предсказуемым.
Классы путают как людей, так и машины
Вдобавок к усложнению организации кода и его повторного использования, классы создают существенный барьер в изучении React. Нужно понимать, как работает this в JavaScript, поведение которого отличается от большинства языков. Приходится помнить про привязку контекста для обработчиков событий. Без использования ES2022 публичных полей класса, код становится многословным. Люди могут прекрасно понимать пропсы, состояние и однонаправленный поток данных, но всё равно путаться с классами. Различия между функциональными и классовыми компонентами в React и тем, когда их использовать, приводят к разногласиям даже между опытными React-разработчиками.
К тому же React существует уже около пяти лет и мы хотим убедиться, что он останется актуальным в течение следующих пяти лет. Как показывают Svelte, Angular, Glimmer и другие технологии, компиляция компонентов перед их исполнением имеет огромный потенциал в будущем. Особенно, если шаблоны не накладывают ограничений. Недавно мы экспериментировали со свёртыванием компонентов с использованием Prepack и увидели первые многообещающие результаты. Однако мы заметили, что классовые компоненты могут приводить к ненамеренным паттернам, сводящим оптимизации на нет. Классы создают сложности для инструментов и сегодня. Например, классы плохо минифицируются, а горячая перезагрузка (hot reloading) ненадёжна и часто ломает их. Наша цель — предоставить API, который повысит вероятность того, что код можно будет оптимизировать.
Чтобы решить эти проблемы, хуки позволяют использовать больше возможностей React без написания классов. Концептуально, React-компоненты всегда были ближе к функциям. Хуки обеспечивают доступ к функционалу, но не обесценивают опыт использования React. Хуки предоставляют императивные лазейки и не требуют от вас изучения сложных функциональных или реактивных подходов.
Стратегия постепенного внедрения
TLDR: Мы не планируем удалять классы из React.
Мы знаем, что React-разработчики сфокусированы на поставке продукта и у них нет времени изучать новый API каждого релиза. Хуки это что-то новое, и возможно, лучше подождать больше примеров и уроков, прежде чем начинать их изучение.
Мы также понимаем, что планка для добавления новых примитивов в React очень высока. Поэтому для любопытных читателей мы подготовили подробный RFC, в котором можно найти больше информации о технических аспектах выбранного дизайна.
Важно понимать, что хуки работают рядом с существующим кодом, поэтому вы можете внедрять их постепенно. Нет спешки переходить на хуки. Мы рекомендуем избегать любых «больших переписываний», особенно для существующих, сложных классовых компонентов. Вам потребуется немного изменить мировоззрение, чтобы начать «мыслить хуками». По нашему опыту, лучше всего сначала попрактиковаться использовать хуки в новых и некритичных компонентах и убедиться, что все в вашей команде чувствуют себя комфортно с ними. После того, как вы попробуете, не стесняйтесь отправить нам свой отзыв, позитивный или негативный.
Мы намерены охватить все возможные варианты использования классов в хуках, но мы всё ещё будем поддерживать классовые компоненты в обозримом будущем. В Facebook десятки тысяч компонентов, написанных в виде классов, и у нас нет абсолютно никаких планов их переписывать. Вместо этого мы начинаем использовать хуки в новом коде параллельно с классами.
Часто задаваемые вопросы
Мы подготовили для вас страницу FAQ с ответами на самые частые вопросы о хуках.
К концу этой страницы вы должны иметь общее представление о том, какие проблемы решают хуки, но многие детали, возможно, остались непонятны. Не беспокойтесь! Давайте перейдём на следующую страницу, где мы изучим хуки на примерах.
Краткий обзор хуков
Хуки — нововведение в React 16.8, которое позволяет использовать состояние и другие возможности React без написания классов.
Хуки — обратно совместимы. На этой странице вы получите общее представление о хуках. Имейте в виду, что это беглый обзор, который больше подойдёт опытным пользователям React. В конце каждого раздела есть вот такой жёлтый блок с детальным объяснением на случай, если вы запутались:
Подробное объяснение
Если вы хотите понять, почему мы добавляем хуки в React, прочтите мотивацию.
Рассмотрим пример, в котором рендерится счётчик. Если вы нажмёте на кнопку, значение счётчика будет инкрементировано.
import React, useState > from 'react'; function Example() // Объявляем новую переменную состояния "count" const [count, setCount] = useState(0); return ( div> p>Вы нажали count> разp> button onClick=() => setCount(count + 1)>> Нажми на меня button> div> ); >
В этом примере, useState — это хук (определение хука дано ниже). Мы вызываем его, чтобы наделить наш функциональный компонент внутренним состоянием. React будет хранить это состояние между рендерами. Вызов useState возвращает массив с двумя элементами, который содержит: текущее значение состояния и функцию для его обновления. Эту функцию можно использовать где угодно, например, в обработчике событий. Она схожа с this.setState в классах, но не сливает новое и старое состояние вместе. Сравнение хука useState и this.state приводится на странице Использование хука состояния.
Единственный аргумент useState — это начальное состояние. В примере выше — это 0 , так как наш счётчик начинается с нуля. Заметьте, что в отличие от this.state , в нашем случае состояние может, но не обязано, быть объектом. Исходное значение аргумента используется только при первом рендере.
Объявление нескольких переменных состояния
Хук состояния можно использовать в компоненте более одного раза.
function ExampleWithManyStates() // Объявляем несколько переменных состояния! const [age, setAge] = useState(42); const [fruit, setFruit] = useState('банан'); const [todos, setTodos] = useState([ text: 'Изучить хуки' >]); // . >
Синтаксис деструктуризации массивов позволяет нам по-разному называть переменные состояния, которые мы объявляем при вызове useState . Так как имён этих переменных нет в API useState , React предполагает, что если вы вызываете useState много раз, вы делаете это в одинаковой последовательности при каждом рендере. Мы расскажем, почему это работает и когда это целесообразно, немного позже.
Что же такое хук?
Хуки — это функции, с помощью которых вы можете «подцепиться» к состоянию и методам жизненного цикла React из функциональных компонентов. Хуки не работают внутри классов — они дают вам возможность использовать React без классов. (Мы не рекомендуем сразу же переписывать существующие компоненты, но при желании, вы можете начать использовать хуки в своих новых компонентах.)
React содержит несколько встроенных хуков, таких как useState . Вы также можете создавать собственные хуки, чтобы повторно использовать их в других своих компонентах. Давайте для начала рассмотрим встроенные хуки.
Вам скорее всего доводилось ранее запрашивать данные, делать подписки или вручную менять DOM из React-компонента. Мы расцениваем эти операции как «побочные эффекты» (или сокращённо «эффекты»), так как они могут влиять на работу других компонентов и их нельзя выполнить во время рендера.
С помощью хука эффекта useEffect вы можете выполнять побочные эффекты из функционального компонента. Он выполняет ту же роль, что и componentDidMount , componentDidUpdate и componentWillUnmount в React-классах, объединив их в единый API. Вы можете найти сравнение useEffect и этих методов на странице использование хука эффекта.
К примеру, этот компонент устанавливает заголовок документа после того, как React обновляет DOM:
import React, useState, useEffect > from 'react'; function Example() const [count, setCount] = useState(0); // По принципу componentDidMount и componentDidUpdate: useEffect(() => // Обновляем заголовок документа, используя API браузера document.title = `Вы нажали $count> раз`; >); return ( div> p>Вы нажали count> разp> button onClick=() => setCount(count + 1)>> Нажми на меня button> div> ); >
Когда вы вызываете useEffect , React получает указание запустить вашу функцию с «эффектом» после того, как он отправил изменения в DOM. Поскольку эффекты объявляются внутри компонента, у них есть доступ к его пропсам и состоянию. По умолчанию, React запускает эффекты после каждого рендера, включая первый рендер. Мы рассмотрим более подробно, как это отличается от классовых методов жизненного цикла на странице использование хука эффекта.
При необходимости вы можете вернуть из эффекта функцию, которая указывает эффекту, как выполнить за собой «сброс». Например, этот компонент использует эффект, чтобы подписаться на статус друга в сети, и выполняет сброс, отписываясь от него.
import React, useState, useEffect > from 'react'; function FriendStatus(props) const [isOnline, setIsOnline] = useState(null); function handleStatusChange(status) setIsOnline(status.isOnline); > useEffect(() => ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); >; >); if (isOnline === null) return 'Загрузка. '; > return isOnline ? 'В сети' : 'Не в сети'; >
В этом примере, React будет отписываться от нашего ChatAPI перед тем, как компонент размонтируется и перед тем, как перезапустить эффект при повторном рендере. Вы можете сделать так, чтобы React пропускал повторные подписки если props.friend.id , который мы передали в ChatAPI , остался без изменений.
Так же как и useState , вы можете использовать более одного эффекта в компоненте:
function FriendStatusWithCounter(props) const [count, setCount] = useState(0); useEffect(() => document.title = `Вы нажали $count> раз`; >); const [isOnline, setIsOnline] = useState(null); useEffect(() => ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); >; >); function handleStatusChange(status) setIsOnline(status.isOnline); > // .
Хуки дают вам возможность организовать побочные эффекты в компоненте по связанным частям (например, добавление или отмена подписки), вместо того, чтобы принуждать вас делить всё согласно методам жизненного цикла.
Подробное объяснение
Вы можете узнать больше о useEffect на странице Использование хука эффекта.
✌️ Правила хуков
Хуки — это функции JavaScript, которые налагают два дополнительных правила:
- Хуки следует вызывать только на верхнем уровне. Не вызывайте хуки внутри циклов, условий или вложенных функций.
- Хуки следует вызывать только из функциональных компонентов React. Не вызывайте хуки из обычных JavaScript-функций. Есть только одно исключение, откуда можно вызывать хуки — это ваши пользовательские хуки. Мы расскажем о них далее.
Мы разработали специальный плагин для линтера, который помогает обеспечивать соблюдение этих правил. Мы понимаем, что эти правила могут показаться немного непонятными и накладывать определённые ограничения, но они очень важны для правильной работы хуков.
Подробное объяснение
Вы можете узнать больше на странице Правила хуков.
Создание собственных хуков
Иногда нужно повторно использовать одинаковую логику состояния в нескольких компонентах. Традиционно использовались два подхода: компоненты высшего порядка и рендер-пропсы. С помощью пользовательских хуков эта задача решается без добавления ненужных компонентов в ваше дерево.
Ранее на этой странице мы рассматривали компонент FriendStatus , который вызывал хуки useState и useEffect , чтобы подписаться на статус друга в сети. Допустим, мы хотим ещё раз использовать эту логику с подпиской, но уже в другом компоненте.
Прежде всего, давайте извлечём эту логику в пользовательский хук useFriendStatus
import React, useState, useEffect > from 'react'; function useFriendStatus(friendID) const [isOnline, setIsOnline] = useState(null); function handleStatusChange(status) setIsOnline(status.isOnline); > useEffect(() => ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); return () => ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); >; >); return isOnline; >
Хук принимает friendID в качестве аргумента и возвращает переменную, которая показывает, в сети наш друг или нет.
Теперь мы можем использовать этот хук в обоих наших компонентах:
function FriendStatus(props) const isOnline = useFriendStatus(props.friend.id); if (isOnline === null) return 'Загрузка. '; > return isOnline ? 'В сети' : 'Не в сети'; >
function FriendListItem(props) const isOnline = useFriendStatus(props.friend.id); return ( li style= color: isOnline ? 'green' : 'black' >>> props.friend.name> li> ); >
Состояния каждого компонента никаким образом не зависят друг от друга. Хуки — это способ использовать повторно логику состояния, а не само состояние. Более того, каждое обращение к хуку обеспечивает совершенно изолированное состояние. Вы даже можете использовать один и тот же хук несколько раз в одном компоненте.
Пользовательские хуки — это в большей степени соглашение, чем дополнение. Если имя функции начинается с ” use ” и она вызывает другие хуки, мы расцениваем это как пользовательский хук. Если вы будете придерживаться соглашения useSomething при именовании хуков, это позволит нашему плагину для линтера найти баги в коде, который использует хуки.
Есть много подходящих случаев, чтобы написать пользовательские хуки, такие как работа с формами, анимация, декларативные подписки, таймеры и, наверное, много других, о которых мы даже не думали. Мы с нетерпением ожидаем увидеть, какие же пользовательские хуки сообщество React сможет придумать.
Есть ещё несколько менее используемых встроенных хуков, которые могут вам пригодиться. Например, с помощью useContext , вы можете подписаться на контекст React без использования каких-либо вложений.
function Example() const locale = useContext(LocaleContext); const theme = useContext(ThemeContext); // . >
А хук useReducer даёт возможность управлять внутренним состоянием более сложного компонента с помощью редюсера.
function Todos() const [todos, dispatch] = useReducer(todosReducer); // .
Подробное объяснение
Вы можете узнать больше обо всех встроенных хуках на странице API-справочника хуков.
Фух, давайте перестанем торопиться и немного охладим пыл! Если вам что-то непонятно или вы хотите узнать о чём-либо более подробно, вы можете начать читать следующие страницы, начиная с документации хука состояния.
Вы также можете просмотреть API-справочник хуков и FAQ хуков.
И наконец, не проходите мимо вступительной страницы, на которой вы узнаете почему мы добавляем хуки и как мы планируем использовать их вместе с классами без необходимости переписывать наши приложения.
React Hooks простыми словами
О хуках в фронтенд-разработке на Хабре писали уже не раз, и в этой статье мы не сделаем великого открытия. Наша цель другая – рассказать про React Hooks настолько подробно и просто без трудной терминологии, насколько это возможно. Чтобы после прочтения статьи каждый понял про хуки всё. Эта статья будет полезна как начинающим React-разработчикам, так и тем, кто хочет, не уходя в глубины документации, получить практическую информацию в сжатом виде.

На заре React-человечества
Когда сообщество впервые познакомилось с функциональными компонентами, они служили только для того, чтобы выводить информацию. У них не было ни состояния, ни методов жизненного цикла. Они были очень простыми – в этом и заключалась их проблема. Часто возникала ситуация, что компонент, написанный в функциональном стиле, должен был в дальнейшем иметь состояние или методы жизненного цикла, а такой возможности не было. Приходилось переписывать их в классовые компоненты, а это далеко не самое интересное занятие для разработчика.
Такое положение дел подтолкнуло разработчиков React к созданию хуков, позволяющих расширить возможности функциональных компонентов либо нивелировать некоторые проблемы, которые могут возникать из-за их специфики. Хуки оказались настолько удобны, что стали основой React-разработки. Рассмотрим подробнее.
Хук useState – простой хук для разработчика, но важный для всего приложения
Начнём с самого простого и важного хука – useState. Из самого названия становится понятно, что он связан с состоянием компонента. Именно благодаря ему у функциональных компонентов появилось состояние.
Приведу простой пример использования данного хука, а потом разберем его подробнее.
const App = () => < const [value, valueChange] = useState(0); return ( ); >;
По сути, этот пример состоит из состояния value, которое содержит в себе целочисленное значение, и кнопки “Увеличить значение на 1”. При нажатии на нее состояние value увеличивается на 1.
Посмотрите на строчку:
const [value, valueChange] = useState(0);
В ней создается состояние и метод, который будет менять это значение. Хук useState по сути принимает в качестве параметра начальное значение, то есть на начальном этапе наш value будет иметь значение 1. И возвращает useState массив из двух элементов: первый – состояние, второй – метод, который будет его изменять. Разработчики хуков использовали довольно изящный подход. При использовании деструктуризации он позволяет задать любое значение состояния и метода минимальным количеством кода.
Обратите внимание еще на одну строчку:
Тут добавлен обработчик события нажатия на кнопку. Поясню: при нажатии на кнопку мы вызываем метод valueChange и отправляем туда новое значение – в нашем случае увеличенное на один.
В остальном всё как с обычным состоянием компонента. Основное отличие: в классовом компоненте мы можем создать только одно общее состояние компонента, а в функциональном – несколько, и они будут независимы друг от друга, но каждое из них будет вызывать рендеринг компонентов.
Хук useContext – сквозь пространство
Чтобы передать какие-то данные в компонент, мы можем использовать props. Но есть и альтернативный способ – context.
Если вы ранее его не использовали, то контекст позволяет передавать данные от родительского компонента к дочернему, минуя промежуточные.
Чтобы было понятнее, создали небольшой пример, который позволит понять его работу. У нас есть три компонента. Первый из них External – внешний, второй – Intermediate, то есть промежуточный, а третий назовем Internal, и он будет внутренним. По сути, все они будут вложены друг в друга. Наша задача – передача данных из компонента External в компонент Internal, минуя Intermediate, так как к нему эти данные отношения не имеют.
import from "react"; const MyContext = createContext("without provider"); const External = () => < return ( ); >; const Intermediate = () => < return ; >; const Internal = () => < const context = useContext(MyContext); return `I am Internal component. I have got the message from External: "$"`; >;
Чтобы использовать контекст, мы создаём объект MyContext, вызывая метод createContext. В компоненте External оборачиваем компонент Intermediate в компонент MyContext.Provider. Тем самым говорим, что все вложенные в него компоненты смогут получить доступ к данным, которые мы передаем, помещая их в параметр value.
Причём они будут доступны только в тех компонентах, в которых нам это нужно. Для этого мы должны использовать хук useContext, а в качестве аргумента у него будет объект MyContext. Хук useContext вернёт нам данные, переданные в параметр value у MyContext.Provider, которые мы поместим в переменную context. Обратите внимание, что в качестве аргумента в createContext мы передали строку (“without context”). Его значение попадет в переменную context в том случае, если вы вдруг забудете создать обертку MyContext.Provider, то есть он поможет не допустить ошибку из-за невнимательности.
Благодаря хуку useContext можно использовать context в функциональных компонентах, и данные будут попадать только в те компоненты, в которых они нужны. Также он избавит от проблемы с drops drilling.
Хуки useEffect и useLayoutEffect – придание жизни компонентам, а точнее – придание методов жизненного цикла
Если вы работали с классовыми компонентами, то знакомы с методами жизненного цикла. Они служат для того, чтобы совершать какие-то операции на разных стадиях жизни компонента. Для этого у нас есть два хука – useEffect и useLayoutEffect. Они похожи между собой, за исключением небольшой разницы в рендеринге. В случае с useLayoutEffect React не запускает рендеринг построенного DOM дерева до тех пор, пока не отработает useLayoutEffect. Если же мы берём useEffect, то React сразу запускает рендеринг построенного DOM, не дожидаясь запуска useEffect.
С помощью этих двух хуков в функциональных компонентах можно смоделировать работу трех методов жизненного цикла – componentDidMount, componentDidUpdate, componentWillUnmount. Более точно их работу имитирует useLayoutEffect, так как в классовых компонентах отрисовка DOM-дерева не запускается до тех пор, пока не отработает метод componentDidMount.
Поскольку эти два хука имеют один и тот же интерфейс, продемонстрируем его на более популярном хуке – useEffect, а для другого всё будет аналогично.
useEffect принимает в себя два аргумента:
- callback. Внутри него вся полезная нагрузка, которую мы хотим описать. Например, можно делать запросы на сервер, задание обработчиков событий на документ или что-то ещё;
- массив, состоящий из аргументов. При изменении значения внутри любого из них будет запускаться наш callback. Именно благодаря этому аргументу мы можем имитировать методы жизненного цикла.
Рассмотрим имитацию componentDidMount. Хук useEffect запускается не только при изменении элементов массива из второго аргумента, но также и после монтирования компонента. Фактически componentDidMount запускается на той же стадии. Если мы укажем в качестве второго аргумента пустой массив, callback запустится на стадии монтирования компонента. А поскольку никаких зависимостей для хука внутри массива мы не задали, то аргумент callback не будет больше запускаться.
const App = () => < useEffect(() =>< console.log("componentDidMount"); >, []); return null; >;
Имитация componentDidUpdate также возможна. Но в случае с хуками есть лучший способ. Поскольку мы можем указать в массиве только те зависимости, которые нам нужны, у нас есть более гибкий аналог метода componentDidUpdate, который запускается при изменении необходимых параметров. Если нужно сделать аналогию componentDidUpdate, в качестве зависимостей можно указать все параметры и состояния. При этом важно учитывать, что useEffect запускается и на стадии монтирования.
const App = () => < useEffect(() =>< console.log("componentDidUpdate"); >, [data]); return null; >;
Теперь рассмотрим имитацию componentWillUnmount. Для этого просто возвращаем из useEffect callback. Практически возвращаемый callback – это и есть аналог componentWillUnmount, который часто применяется для отвязывания обработчиков событий документа. В случае с функциональным компонентом мы будем отвязывать их внутри возвращаемого callback.
const App = () => < useEffect(() => < return () =>< console.log("componentWillUnmount"); >; >, []); return null; >;
Хук useRef – прямая связь с узлами и не только
Бывают ситуации, когда необходимо обратиться к какому-то DOM-объекту напрямую. Для этого существует хук useRef.
const App = () => < const ref = useRef(); useEffect(() =>< console.log(ref.current); >, []); return />; >;
Мы создаём объект ref и указываем его в качестве элемента, обозначающего DOM-объект, к которому мы хотим обратиться, а также прописываем этот объект в качестве параметра. Далее мы можем взаимодействовать с Dom-объектом напрямую, как если бы мы нашли его с помощью селектора. Для этого используем свойство current у объекта ref.
Помимо этого можно применить useRef, если нам нужно будет запомнить данные в компоненте. Но не рекомендуем вызывать рендеринг в случае изменения состояния компонента. В классовых компонентах для этой цели обычно используют this.
Хук useReducer – снова идём сквозь пространство
Разработчикам React так понравился Redux, что они решили добавить его аналог в состав React. Этот хук позволяет вынести данные из компонентов.
import from "react"; const initialState = ; function reducer(state, action) < switch (action.type) < case "increment": return < . state, count: state.count + 1, >; case "decrement": return < . state, count: state.count - 1, >; default: throw new Error(); > > const App = () => < const [state, dispatch] = useReducer(reducer, initialState); return ( <> >); >;
У него есть преимущество: вне зависимости от того, как компоненты нашего приложения будут вложены друг в друга, мы сможем отобразить данные в любом компоненте.
Хук useMemo – оптимизируй вычисления
Этот хук позволяет не производить одни и те же вычисления много раз. Допустим, у нас есть следующий компонент:
const MyComponent = () => < const sqrt = a * a; return ( А в квадрате: B: ); >;
В этой ситуации компонент перерендеривается в том случае, если изменяется один из параметров – a или b. Представим, что у нас много раз изменяется параметр b, при этом параметр a остаётся прежним. В таком случае мы много раз вычисляем одно и то же произведение, которое помещаем в переменную sqrt. Но зачем нам это, если параметр a в этом случае остаётся прежним? Получается, мы лишний раз нагружаем наш ПК вычислениями одного и того же. И хотя в данном случае операция произведения не самая “энергозатратная”, в других ситуациях возможна лишняя нагрузка. Избежать избыточных вычислений нам помогает хук useMemo. Давайте немного преобразуем наш пример.
const MyComponent = () => < const sqrt = useMemo(() =>a * a, [a]); return ( А в квадрате: B: ); >;
Тут всё осталось по-прежнему, за исключением ситуации, когда мы обернули наше произведение в хук useMemo, в который передали callback и массив зависимостей. По сути они работают также, как и в useEffect: как только меняется какая-то зависимость из массива, запускается callback, который рассчитывает другое значение. Если ни одна зависимость не поменялась, то при рендеринге в переменную будет подставлено предыдущее вычисленное значение.
Хук useCallback – ещё больше оптимизации
В силу того, что функциональный компонент – это функция, при каждом рендеринге запускается всё, что объявлено в ней. Предположим, что мы создаем внутри компонента функцию и передаем ее в дочерний компонент. Это самая обыкновенная практика. Она часто встречается, когда нам нужно из дочернего компонента изменить что-то в родительском. Создадим небольшой пример, чтобы это продемонстрировать.
const ControlPannel = memo(() => < return ( ); >); const App = () => < const [value, valueChange] = useState(Math.random()); const changer = () =>valueChange(Math.random()); return ( ); >;
В данном примере представлены два компонента, один из них – ControlPanel, который отвечает за стилизацию контрольной панели. В ней всего одна кнопка, которая меняет состояние родительского компонента. В качестве параметра в него передан метод changer, который внутри себя содержит вызов метода valueChange, он-то и обновляет состояние. Для простоты изменим значение состояние, просто поместив туда случайное число. Мы специально обернули ControlPanel в memo, чтобы этот компонент перерисовывался только в том случае, если изменились его параметры. Однако в данном случае у нас возникает проблема: при каждой отрисовке компонента App мы будем заново создавать метод changer. Хотя сигнатура у метода будет одинаковой, каждый раз будет создан новый метод, следовательно, у ControlPanel будут происходить повторные рендеринги, но по сути ничего не меняется. В этом случае в качестве параметра будут передаваться разные реализации одной и той же функции.
Избежать этого поможет useCallback.
const ControlPannel = memo(() => < return ( ); >); const App = () => < const [value, valueChange] = useState(Math.random()); const increment = useCallback(() =>valueChange(Math.random()), []); return ( ); >;
Благодаря этому хуку мы единожды создаем метод и обновляем его только тогда, когда меняется какой-то из параметров, которые мы поместим в качестве элемента массива и передаем вторым аргументом – по аналогии с массивами в других хуках. Так мы мемоизируем наш метод,и у нас не происходит лишний рендеринг, как это было ранее.
Пользовательский хук – создай мир своими руками
Пользовательские хуки – это те же самые функции, которые внутри себя используют какие-либо из стандартных хуков. Единственное требование, которое здесь необходимо соблюдать – относиться к ним, как к хукам. То есть, соблюдать правила, что мы используем при работе с хуками: не вызывать их внутри условных конструкций (таких, как if или switch) и внутри циклов (например for), а также не использовать хуки внутри колбэков других хуков.
Для того чтобы все в команде соблюдали указанные правила и понимали, что это хуки, а не просто методы, называть их лучше в формате useИмяХука.
Рассмотрим пример пользовательского хука:
const useSingleLog = () => < useEffect(() =>< console.log("I am single log"); >, []); >;
Как вы видите, мы создали хук, который позволяет нам единожды вывести строку в консоль. Он содержит в себе хук useEffect. По сути, мы можем использовать его в любых компонентах.
Спасибо за внимание! Надеемся, что материал был вам полезен.
P.S. Если у вас есть базовые знания Frontend и вы хотите их углубить, приглашаем зарегистрироваться на наш онлайн-практикум (до 28 февраля). Также 24 февраля проведем вебинар для всех желающих.
Спасибо за внимание!
Авторские материалы для frontend-разработчиков мы также публикуем в наших соцсетях – ВКонтакте и Telegram.