
В этой статье мы поговорим о том, что такое React-хуки, о том, что отличает их от других механизмов библиотеки, и о том, почему они — это лучшее, что случилось с React.
О возникновении хуков React
Изначально в библиотеке React использовались, в основном, компоненты, основанные на классах. Применение таких компонентов может потребовать приложения чрезмерных усилий в ходе разработки, так как программисту постоянно приходится переключаться между классами, компонентами высшего порядка, свойствами рендеринга. А с появлением хуков React можно, решая те же задачи, что и раньше, не переключаясь между различными механизмами, просто пользоваться функциональными компонентами. Хуки значительно улучшили React из-за того, что с их помощью можно писать код, который получается проще, чем код, который писали раньше, но при этом позволяет быстрее и эффективнее реализовывать похожий функционал. А ещё можно, не пользуясь классами, работать с состоянием компонентов и с методами их жизненного цикла.
Вот примеры кода, иллюстрирующие использование компонентов, основанных на классах, и функциональных компонентов.
Компонент, основанный на классах:
import React, { Component } from 'react'
export default class Hello extends Component {
render() {
return(
<div>
Hello World!
</div>
)
}
Этот компонент выводит в DOM элемент
<div>
с сообщением Hello World!
.А вот — код функционального компонента, решающего ту же задачу.
import React from 'react'
export default function Hello() {
return (
<div>
Hello World!
</div>
)
}
Сравнение этих двух примеров показывает, что код функционального компонента гораздо проще кода аналогичного компонента, основанного на классах. Для его использования не нужно создавать экземпляр класса, не нужно вызывать
render()
. Достаточно просто вызвать нужную функцию. Хуки React обладают многими достоинствами, они способны помочь программисту в решении множества задач. Одна из главнейших сильных сторон хуков заключается в том, что они упрощают работу с методами жизненного цикла компонентов React.Да, обратите внимание на то, что хуки нельзя использовать в компонентах, основанных на классах.
Как хуки упрощают работу с методами жизненного цикла компонентов?
Среди методов жизненного цикла React-компонента можно отметить те, которые вызываются при его монтировании, обновлении и размонтировании.
- Монтирование — это вставка элементов в DOM.
- Обновление — это, как можно судить по названию, обновление элементов DOM.
- Размонтирование — это удаление элементов из DOM.
Вот схема, на которой представлены различные методы жизненного цикла React-компонента.
Методы жизненного цикла React-компонента
Изначально эти методы можно было использовать только при применении компонентов, основанных на классах. Это обычно было связано с необходимостью работать с кодом, который достаточно сложно писать и читать. Если же пользоваться функциональными компонентами и хуками — решение тех же задач упрощается.
Предположим, нам нужно загрузить данные с использованием метода жизненного цикла
componentDidMount()
в компоненте, основанном на классах:import React, { Component } from 'react'
import Axios from 'axios'
export default class Hello extends Component {
constructor(props) {
super(props);
this.state = { name: ""};
}
componentDidMount() {
Axios.get('/api/user/name')
.then(response => {
this.setState({ name: response.data.name })
})
}
render() {
return (
<div>
My name is {this.state.name}
</div>
)
}
}
А теперь решим ту же задачу в функциональном компоненте, пользуясь хуками
useState
и useEffect
:import React, { useEffect, useState } from 'react'
import Axios from 'axios'
export default function Hello() {
const [Name, setName] = useState("")
useEffect(() => {
Axios.get('/api/user/name')
.then(response => {
setName(response.data,name)
})
}, [])
return (
<div>
My name is {Name}
</div>
)
}
Этот код загружает нужные данные с использованием Axios API и выводит их в DOM. Хуки
useEffect
и useState
позволяют писать более эффективный и компактный код, чем код, который пишут при использовании компонентов, основанных на классах. Такой код получается понятнее, с ним легче работать. При использовании методов жизненного цикла в компонентах, основанных на классах, приходится по-отдельности работать с методами componentDidMount()
, componentDidUpdate()
, componentWillUnmount()
, а при использовании хуков можно просто сделать всё, что нужно, с помощью useEffect
.Хуки облегчили изучение React
Компоненты, основанные на классах, всегда были несколько громоздкими и непонятными конструкциями, особенно учитывая то, что их применение ведёт к тому, что механизмы управления состоянием компонента и многократного использования кода кажутся сложнее, чем они есть на самом деле. Это привело к тому, что многие новички избегали React, выбирая более «лёгкие» библиотеки и фреймворки. А с появлением хуков изучить React стало легче, чем прежде. Это стало одной из причин роста популярности React.
Вопросы о различных библиотеках и фреймворках на Stack Overflow
На этом графике виден постоянный рост интереса к React на Stack Overflow, продолжающийся уже много лет. Здесь же можно сравнить процент вопросов о React с процентом вопросов о других популярных JavaScript-инструментах. Этот график доказывает то, что разработчики стали чаще пользоваться библиотекой React после появления хуков.
О некоторых хуках и их предназначении
До появления хуков организация кода компонентов на основе методов жизненного цикла вынуждала разработчиков «разбрасывать» по разным компонентам код, направленный на реализацию схожей логики. Для решений этой и других мелких проблем в React были введены функциональные компоненты, что позволило упростить код и сделать его более гибким. Ещё одна задача, которую постоянно приходилось решать разработчикам с использованием не особенно удобных механизмов, заключалась в изменении состояния компонентов. Для решения этой задачи теперь используется хук
useState
.▍Хук useState
Вероятно,
useState
— это самый распространённый React-хук. Он позволяет работать с переменными состояния в функциональных компонентах.const loadingTuple = React.useState(true)
const loading = loadingTuple[0]
const setLoading = loadingTuple[1]
loading // true
setLoading(false)
loading // false
Здесь
useState
принимает единственный аргумент: исходное значение состояния. Он возвращает массив с переменной state
и с функцией для обновления этого состояния. После этого у программиста есть всё, что нужно для работы с состоянием в функциональном компоненте.Следующий хук, который мы рассмотрим, направлен на работу с методами жизненного цикла в функциональных компонентах. Он называется
useEffect
.▍Хук useEffect
Хук
useEffect
помогает программисту выполнять побочные эффекты в функциональных компонентах. То есть — вызывать функции, которые нужно выполнить после обновления DOM. Он заменяет некоторые события, позволяя вызывать функцию при изменении одной или нескольких переменных. Он принимает два аргумента: функцию и необязательный массив. Эта функция определяет то, какой именно «побочный эффект» нужно выполнить, а в массиве указывают переменные, за изменениями которых нужно наблюдать.▍Другие хуки
-
useContext
: позволяет работать с контекстом — с механизмом, используемым для организации совместного доступа к данным без необходимости передачи свойств. -
useRef
: позволяет напрямую обращаться к DOM в функциональных компонентах. Обратите внимание на то, чтоuseRef
, в отличие отsetState
, не вызывает повторный рендеринг компонента. -
useReducer
: хранит текущее значение состояния. Его можно сравнить с Redux. -
useMemo
: используется для возврата мемоизированного значения. Может применяться в случаях, когда нужно, чтобы функция возвратила бы кешированное значение. -
useCallback
: применяется в ситуациях, когда дочерние элементы компонента подвергаются постоянному повторному рендерингу. Он возвращает мемоизированную версию коллбэка, которая меняется лишь тогда, когда меняется одна из зависимостей.
Выше мы коснулись лишь некоторых наиболее часто используемых хуков React. Если вы хотите углубиться в их изучение — загляните сюда.
Итоги
Мы, при создании React-компонентов, пользовались классами из-за того, что в своё время для работы с состоянием или для реализации методов жизненного цикла нельзя было воспользоваться функциональными компонентами. Хуки React значительно упростили решение старых задач, дав нам возможность писать код, который отличается лучшей пригодностью к созданию нового функционала методом композиции, который получается гибче, чем прежде, который легче расширять. Многие компании используют React в роли своей основной фронтенд-библиотеки, что приводит к тому, что всё больше и больше разработчиков осваивают React.
Пользуетесь ли вы хуками React в своих проектах?

Комментарии (52)
anonymous
00.00.0000 00:00Mox
15.11.2021 21:11+4Я сначала в штыки воспринял хуки, и в старых проектах заблокировал их внедрение ради консистентности подхода
А в новых попробовал писать, в итоге пришел к тому, что хуки офигенны, и прозрение случилось в тот момент, когда я начал писать свои кастомные хуки.
Что-то вроде
const { handleSubmit, handleListItemToggle, listItems } = useMyCustomScreenHook();
В итоге
Функциональные компоненты получились гораздо чище классов, ведь из своего хука я экспортирую только нужные методы, абстрагируясь от организации state и того как именно это работает в React.
Хуки можно разделить, если они отвечают за разные логические моменты экрана, получить более читаемый код
Некоторые хуки можно сделать системными. Например - в одном проекте понадобилось пулить несколько сторонних REST API для получения статусов, и был написал хук для этого, который оч красиво встал во все места где это нужно
P.S. Пишу про экраны, потому что сейчас делаю проекты на React Native.
wheercool
16.11.2021 00:32+10Все смешалось люди, кони.
Особенно понравилась логическая цепочка: появление хуков сделало реакт более понятным, поэтому кол-во вопросов на SO возросло )
Alexandroppolus
16.11.2021 09:05+1Старый добрый реактовский Context API был ужасен. useContext - одно из самых крутых улучшений. Но не единственное. Как уже говорилось, проще декомпозировать логику (которую можно реализовывать на классах и хранить экземпляры в рефах).
Elvizmarco
16.11.2021 14:52У меня вот вопрос, уже сто раз видел такие примеры. Хоть кто-то пишет код запроса в компонентах? А не создается какая то оберточка для екшена?
amakhrov
16.11.2021 20:52+2Экшн - это если мы данные хотим в глобальный стор поместить. Для переиспользования в других местах приложения, к примеру. Но вполне себе есть случаи, когда данные одноразовые и нужны только в одном компоненте. Общий стор для этого избыточен.
Кроме того, вполне себе есть паттерн без thunks - компонент реализует асинхронную логику, и уже диспатчит в стор готовые результаты. (опять же для случая, когда эту логику переиспользовать нигде не требуется)
dedotmoroz
16.11.2021 14:52+1Мне в хуках не хватает второго аргумента в setState -
setState(updater, [callback])
Приходится колхозить.
exewebru
16.11.2021 21:05+1Ещё нет forceUpdate
faiwer
16.11.2021 21:36export const useForceUpdate = () => { const [, setState] = useState({}); return useCallback(() => { setState({}); }, []); }
На самом деле, большая часть того, чего нет, пишется в три секунды.
exewebru
17.11.2021 09:07+1Какой смысл в библиотеке, если банальные вещи попросту не реализованы? Хуки выглядят как полуфабрикат.
ivgrin
16.11.2021 14:52+1Этот график доказывает то, что разработчики стали чаще пользоваться библиотекой React после появления хуков.
График также "доказывает" увеличение популярности библиотеки с момента появления COVID-19.
marshinov
Мне одному классы больше нравились?
Drag13
Не только вам. Мне функциональные компоненты очень нравятся, но только до тех пор пока они чистые. Может я просто старый.
marshinov
Вот да. Как только неявный стейт где-то за кадром в хуках "проще" - ну хз. Может проще, пока не думаешь, как это работает под ковром.
alexesDev
Было проще, если бы react явно просил передавать currentNode из своего дома как аргумент.
const MyComponent = (props, node) => { useState(1, node)
currentNode есть, но спратана в кишках.
marshinov
Да, так было бы максимально понятно. Ну и всякие «нельзя в циклах, нельзя в if, надо всегда в одном порядке, чтобы мы в глобальной хешмапе (ну или что там внутри) поняли че куда сувать». Понятно, что они так две переменные сэкономили, но стоило ли? Т.е. когда в редаксе предлагали шмотки копипастить - было норм, а теперь резко боремся за каждую переменную. Ну может и хрен с ним, все-равно везде ложь: рантайм спекулирует, планировщик делает че хочет, процессор конвееризирует все и вообще из кешей вместо памяти читает. Если кругом хаки и ложки нет, то может хрен с ним с неявным стейтом.
Fen1kz
В классах это "под ковром" ещё хуже. Суть в переиспользовании логики — если в классах вы либо копипастите, либо начинаете делать HoC'и, то с хуками можете легко скомпоновать.
Грубо говоря пока вы пишете
Классы реально кажутся лучше и проще, а вот когда у вас есть SomeComponent который может быть Draggable, Droppable, Transparentable и Чегоготамble, то вы городите жуткую пирамиду из НоС'ов:
Ой, погодите, почему так скучно, они же конфигурируемые?!
Потом вы устаете это писать руками и ставите recompose (или что там сейчас, подскажите кто знает)
Получается так:
Звучит неплохо, но теперь у вас:
1) конфиги больше самих компонентов
2) Очень больно передавать данные между этими НоС'ами
Вот пример из моей игры:
InteractionTarget и AnimatedHOC — это логика которая шарится между многими классами.
Вот и получается, что при такой сложности классы уже не тянут то что нужно и всю эту логику можно вынести в хуки, что сделает её более прозрачной:
tl;dr
1) что классы, что хуки на простых компонентах примерно одинаковые по сложности
2) С увеличением количества реиспользования программирование на классах смещается от самих классов к HoC'ам. Или к renderProps'ам
А вот у них обоих хуки выигрывают с большим отрывом.
marshinov
Ну может для игр так. У меня таких страшных HoC'ов не было никогда, но ваша точка зрения понятна.
Fen1kz
Для игр, для библиотек и для хороших энтерпрайз проектов.
Вы вот со своими классами что будете делать, если вам нужно определить stateful-поведение отдельно от сущностей? Копипастить, как обычно?
EDIT: Отличный пример это Apollo Client
Вот, почитайте как это было раньше: https://www.apollographql.com/docs/react/api/react/hoc/ (NSFW)
strannik_k
Ну, если не говорить про реакт и пойти, например, в сторону игр (я их когда-то разрабатывал), то многие большие игровые проекты успешно пишутся на классах. Как бы, проблема, которая недавно решена с помощью функциональных компонентов с хуками, уже лет 20-30 как решена на классах в геймдеве.
В случае классов можно не городить кучу оберток с помощью декораторов (HOC), а выносить логику из компонента в другие классы/объекты или функции, а также воспользоваться другими паттернами композиции, например стратегией. Если интересно, можете почитать о различных подходах в моей статье: https://habr.com/ru/post/545368/
Fen1kz
Хорошая статья, спасибо.
Правда в случае этой статьи, мы все-таки говорим про реакт и я доказывал именно преимущество хуков над классами в реакте, а не вообще по жизни
strannik_k
Ок, мне было не понятно. Хотел сказать, что проблема не в самих классах, а в реализации компонентов на классах. Можно было сделать лучше.
faiwer
Мне кажется в любых больших проектах так. Не только в играх. Эти HoC иногда собираются в такие безумные пирамидки… К примеру у нас на проекте не
compose
изredux
а самописный методpipe
. А так как TypeScript то он ещё и типизирован:То и дело приходится заходить и дописывать очередной уровень. С hook-ми ничего подобного не испытываем. Это просто код. Им можно жонглировать как угодно, главное не вызывать хуки опционально и в нефиксированных циклах (это кстати напрягает).
marshinov
Ну redux сам по себе копипасту создает:)) Причем в ng эта проблема просто не существует из-за наличия сервисов.
faiwer
А redux тут вообще не причём. Наши HoC с ним не связаны. Чаще всего это всякие context.Provider-ы разных мастей.
P.S. а причём тут Angular?
marshinov
Просто сравниваю. У ng тоже много родовых травм (например, html-шаблоны), но там хорошо сделана структура приложения: feature modules, services, components и везде четко написано, что куда нужно совать, а что не нужно. И "It’s hard to reuse stateful logic between components" просто не существует в ng: делаешь сервис и ничего не hard to reuse. И главное эта логика очень простая и понятная для любого программиста с опытом в enterprise. Хуки вводят новый концепт, который надо учить. Для меня это выглядит как сущность, которую наплодили с понятной надобностью конечно... Но что мешашло IOC или подобие partial application прикрутить, чтобы внедрять зависимости? Типа с хуками проще? Ну может.
Fen1kz
Уже не очень помню ангуляр 1, но там, кажется, нельзя делать так, чтобы сервис был на каждый инстанс компонента. Во втором это норма?
marshinov
1 и 2+ - это вообще 2 разных фреймворка, там почти все переделали.
essome
Во втором можно делать как угодно, но все же сервис на каждый инстанс компонента нужен очень редко.
Drag13
И, кстати, в Angular есть директивы, они могут как раз навешивать кастомную логику на элемент, тот же draggable.
LEXA_JA
Хуки это в первую очередь способ сделать независмый, переиспользуемый кусок логики привязанный к жизненному циклу компонента. Это замена для render props и HoC's (и ранне миксинов) в первую очередь. Со старыми методами была куча проблем, связанная с тем, что все общались через общие пропсы, конфликты имен и т.д. Просто хуки используются и как замена локальному стейту компонента, как в классовых компонентах.
Для IoC используется React Context. Вообще многие библиотеки выглядят в таком виде:
const client = new Client();
<ClientProvider.Provider client={client}>
{children}
</ClientProvider.Provider>
//
const client = useClient()
Есть класс отвечающий за логику. Он закидывается в компоненты через контекст. И есть хук который отвечает за привязку к жизненному циклу компонента.
Мне кажется в таком виде похоже на сервисы.
marshinov
Вы предлагаете смотреть на хуки как на короткие версии render props/hoc/миксины, лишенные проблем конфликта имён в props, а неявный useState и useEffect (жизненный цикл) воспринимать как аспект реализации и не горевать, что их за кулисы убрали? + как бонус компоненты видны без обёрток в консольке. Правильно понял?
essome
А зачем это? За 5 лет использования ts в angular не видел ничего подобного, что этот код делает?
faiwer
В Angular-е ООП, а в ООП не требуется композиция функций, поэтому и не сталкивались. Вам по сути нечего композировать, т.к. большая часть ваших функций это методы классов. А когда у вас половина проекта это тысячи небольших функций, которые вы с друг другом по разному стыкуете, тогда и начинаются все эти примитивы и пайп-операторы.
Формально можно и без них, но читаемость страдает. Скажем:
В React компоненты это, чаще всего, просто функции. И чтобы добавить к ним какую-нибудь типовую функциональность часто используют HoC-и (хоки). Это функции-врапперы, которые принимают на входе один компонент, а возвращают обёртку над ним, которая внутри вызывает тот самый компонент, но теперь с перламутровыми пуговицами. И в клинических случаях такие матрёшки могут состоять из 10 уровней.
marshinov
Есть ощущение, что это горе от ума и слишком мелко нарезанные компоненты просто
faiwer
Нет. Ваши ощущения вас обманывают
Я повторю, то что вы их не используете, это не потому, что у вас архитектура правильнее (или неправильнее), а потому что в Angular ООП. А в ООП почти все функции в проекте завязаны на state своего instance-а класса. Т.е. вы в любом случае их не за pipe-ете, т.к. без this state-а ваши методы не работают. Вам просто нечего pipe-ать. У вас своя собственная вселенная с ООП-паттернами, декораторами, IoC контейнерами и пр… Можно было бы тоже сказать, что это всё "горе от ума", но нет, это просто свои инструменты, каждому из которых есть определённое назначение.
P.S. в React пожалуй нет никакой границы вроде "тут я слишком мелко нарезал". Даже однострочные компоненты — ОК. Например такие:
Это не имеет никаких больших penalty по производительности и отлично сказывается на качестве кода. Особенно когда таким образом достигается разделение business-логики и презентационной. Например в UI библиотеках у любого маломальски сложного компонента очень много допустимых props. И условная кнопка может быть:
Удобно когда вся эта мишура вынесена куда-нибудь с глаз долой и в реальной форме вы видите код вида:
В React нет никаких проблем с тем чтобы раздробить ваши компоненты до любого уровня. Хоть каждый тег компонентом обернуть (если на то есть объективная причина).
markelov69
Ведь так оно и есть на самом деле.
Посмотрите на жителей психбольницы, у них там тоже другой мир со своими правилами.
marshinov
Монады и функторы к хукам не имеют никакого отношения, если только не считать, что хуки - это a la state monad in disguise. Я же не говорю, что есть проблемы с дроблением. Я говорю, что, наоборот, не надо так мелко. Хуки задачу свою вроде как решают, просто «своим особым способом». В итоге есть накопленный опыт в других технологиях и есть особый react way, который как-бы функциональный, но на самом деле не совсем.
faiwer
markelov69
Нет. Очень тонкая грань, все индивидуально, касаемо каждого конкретного компонента и случая.
faiwer
Я выше привёл два примера (
<Separator/>
и<SubmitButton/>
). Я нарушил там эту "грань"? Если да, то почему? Если нет, то можно пример где "грань" нарушена? Ну т.е. что такого нужно сделать чтобы было слишком "мелко нарезано"? И почему?Я понимаю почему:
Это перебор, в большинстве случаев. Но подобного со стороны React не встречал.
amakhrov
Если вы конкретно про перегруженную функцию pipe - то в angular вы наверняка используете Observable#pipe по сто раз на дню. Загляните под капот - увидите ту же самую простыню из перегруженных сигнатур: https://github.com/ReactiveX/rxjs/blob/master/src/internal/Observable.ts#L349
Drag13
У меня вот такое мини
Fen1kz
Хоть сам статью пиши, потому что эта убогая что капец. Хуки у нас появились оттого что лайвсайкла нет в функциональных компонентах бть, ага.
Документация реакта, ПЕРВЫЙ пункт в мотивации:
Хоть бы один пункт из неё взяли >..<
wheercool
Заворачивайте в декораторы и не парьтесь
essome
Вы в примере с классами вписали конфиги, а с хуками нет, что-бы код был короче)
В angular на эти все вещи создается по директиве, которая независима от класса и может работать с любым (или по желанию определенным) тегом/компонентом.
В таком случа если у нас есть компонент А которому нужно добавить draggable - нам не нужно менять компонент, вместо этого мы навешиваем директиву при использовании компонента.
Директив при этом можно навесить сколько угодно.
Может просто в react не умеют в классы?
faiwer
React определённо не умеет в классы. Даже не пытаются. Они уже почти deprecated.
essome
Можно, но по факту нельзя, так как хуки стандарт в реакте сейчас.
marshinov
Плюс это все пиарят как что-то хорошее и "простое". "Классы сложно, поэтому давайте вот на хуках". Але, если вам классы сложно, то какие нафиг "функциональные компоненты"?