usestate react js что это
Using the State Hook
Hooks are an upcoming feature that lets you use state and other React features without writing a class. They’re currently in React v16.8.0-alpha.1.
The previous page introduced Hooks with this example:
We’ll start learning about Hooks by comparing this code to an equivalent class example.
Equivalent Class Example
If you used classes in React before, this code should look familiar:
You might be wondering why we’re using a counter here instead of a more realistic example. This is to help us focus on the API while we’re still making our first steps with Hooks.
Hooks and Function Components
As a reminder, function components in React look like this:
You might have previously known these as “stateless components”. We’re now introducing the ability to use React state from these, so we prefer the name “function components”.
Hooks don’t work inside classes. But you can use them instead of writing classes.
Our new example starts by importing the useState Hook from React:
What is a Hook? A Hook is a special function that lets you “hook into” React features. For example, useState is a Hook that lets you add React state to function components. We’ll learn other Hooks later.
When would I use a Hook? If you write a function component and realize you need to add some state to it, previously you had to convert it to a class. Now you can use a Hook inside the existing function component. We’re going to do that right now!
There are some special rules about where you can and can’t use Hooks within a component. We’ll learn them in Rules of Hooks.
Declaring a State Variable
In a class, we initialize the count state to 0 by setting this.state to < count: 0 >in the constructor:
What do we pass to useState as an argument? The only argument to the useState() Hook is the initial state. Unlike with classes, the state doesn’t have to be an object. We can keep a number or a string if that’s all we need. In our example, we just want a number for how many times the user clicked, so pass 0 as initial state for our variable. (If we wanted to store two different values in state, we would call useState() twice.)
Now that we know what the useState Hook does, our example should make more sense:
You might be wondering: why is useState not named createState instead?
When we want to display the current count in a class, we read this.state.count :
In a function, we can use count directly:
In a class, we need to call this.setState() to update the count state:
In a function, we already have setCount and count as variables so we don’t need this :
Let’s now recap what we learned line by line and check our understanding.
This might seem like a lot to take in at first. Don’t rush it! If you’re lost in the explanation, look at the code above again and try to read it from top to bottom. We promise that once you try to “forget” how state works in classes, and look at this code with fresh eyes, it will make sense.
Tip: What Do Square Brackets Mean?
You might have noticed the square brackets when we declare a state variable:
The names on the left aren’t a part of the React API. You can name your own state variables:
You might be curious how React knows which component useState corresponds to since we’re not passing anything like this back to React. We’ll answer this question and many others in the FAQ section.
Tip: Using Multiple State Variables
Declaring state variables as a pair of [something, setSomething] is also handy because it lets us give different names to different state variables if we want to use more than one:
You don’t have to use many state variables. State variables can hold objects and arrays just fine, so you can still group related data together. However, unlike this.setState in a class, updating a state variable always replaces it instead of merging it.
We provide more recommendations on splitting independent state variables in the FAQ.
Как устроен ReactJS. Пакет React
Большинство людей, работающих во фронтенде, так или иначе сталкивались с реактом. Это JavaScript библиотека, помогающая создавать крутые интерфейсы, в последние годы набрала огромную популярность. При этом, не так много людей знает, как она работает внутри.
В первую очередь, сделаем небольшой пример, на основе которого будем рассматривать этот пакет. Наше мини-приложение будет выглядеть так:
Кому интересно поэкспериментировать и посмотреть, во что превращает ваш код бабель – babel repl.
React.createElement
Итак, мы получили множество вызовов React.createElement() и время посмотреть что же делает эта функция. Опишем на словах (а можно и в файл заглянуть – ReactElement.js).
В первую очередь она проверяет есть ли у нас пропсы (в коде объект с пропсами, который мы передали, называется config ).
Далее идут следующие шаги:
Когда мы подготовили все данные, мы вызываем внутреннюю функцию, которая создаёт объект, описывающий наш компонент. Выглядит этот объект следующим образом:
Если переводить на наш пример, то результат React.createElement(App, null) выглядить это будет так:
Кроме того, в дев режиме у нас появится дополнительное поле, которое будет использоваться для отображения красивого стека с именем файла и строкой:
Подведём маленький итог из того, что мы увидели выше. Пакет react выступает переводчиком между нами и остальными пакетами, которые работают далее над нашим приложением, переводя наши вызовы в слова, понятные, например, реконсайлеру.
React.useState
На самом деле, говорить много тут не придётся. По сути, пакет является фасадом, через который наши вызовы идут к внутренним сущностям.
Так, useState — это ни что иное, как две строчки кода:
Что дальше
Финал
5 React-хуков, которые пригодятся в любом проекте
Хочу рассказать о пяти простых React-хуках, которые пригодятся в любом проекте. Причём, полезность этих хуков не зависит от того, в каком именно приложении их будут использовать. Описывая каждый из них, я рассказываю о его реализации и привожу пример его использования в клиентском коде.
Хук useModalState
Модальные окна широко используются в веб-приложениях, они применяются в самых разных ситуациях. При работе с подобными окнами быстро приходит понимание того, что управление их состоянием — это трудоёмкая задача, решая которую, приходится постоянно выполнять одни и те же рутинные действия. А если имеется код, написание которого отнимает немало сил и времени, код, который приходится постоянно повторять в разных местах приложения, это значит, что такой код имеет смысл абстрагировать, оформив в виде самостоятельной сущности. Хук useModalState — это именно такой код, используемый для управления состоянием модальных окон.
Собственные версии этого хука предоставляют многие библиотеки. Одна из них — это Chakra UI. Если вас интересуют подробности об этой библиотеке — вот мой материал о ней.
Реализация useModalState весьма проста, даже тривиальна. Но опыт подсказывает мне, что гораздо лучше пользоваться им, чем постоянно заново писать код для управления состоянием модальных окон.
Вот код этого хука:
А вот — пример его использования:
Хук useConfirmationDialog
Хук useConfirmationDialog тоже имеет отношение к модальным окнам. И им я тоже пользуюсь довольно часто. Если пользователь некоего приложения выполняет какие-то важные действия, вроде удаления чего-либо, у него принято запрашивать подтверждение выполнения подобных действий. Поэтому такую логику имеет смысл абстрагировать в виде хука. Вот — один из вариантов реализации хука useConfirmationDialog :
Вот — пример его использования:
Тут стоит обратить внимание на то, что эта реализация useConfirmationDialog нормально работает до тех пор, пока в модальном окне подтверждения операции нет управляемых элементов, представленных полями для ввода данных. Если в вашем окне такие элементы имеются — лучше создать для такого модального окна отдельный компонент. Дело тут в том, что вам вряд ли понравится то, что содержимое модального окна, включая и такие поля, будет повторно рендериться каждый раз, когда пользователь вводит в поля какие-то данные.
Хук useAsync
Грамотная поддержка асинхронных операций в приложении — это задача, решить которую сложнее, чем кажется на первый взгляд. Так, может иметься множество переменных, хранящихся в состоянии, за которыми нужно наблюдать в процессе выполнения подобных операций. Приложение может сообщать пользователю о ходе выполнения асинхронной операции, показывая ему индикатор прогресса. Кроме того, нужно обрабатывать ошибки асинхронных операций и, если они происходят, выдавать адекватные сообщения о них. В результате наличие в React-проекте хорошо проработанного фреймворка, обеспечивающего поддержку асинхронных операций, окупится сторицей. Хук useAsync может оказаться полезным именно для решения вышеописанных задач. Вот его код:
А ниже показан пример его использования:
Такой хук несложно написать самостоятельно. Я часто поступаю именно так. Но вам, возможно, имеет смысл присмотреться к более зрелой библиотечной реализации подобного механизма. Например — к этой.
Хук useTrackErrors
Валидация форм — это ещё одна задача, решаемая в рамках React-приложений, которую программисты находят достаточно скучной. Учитывая это, можно отметить, что существует множество отличных библиотек, помогающих управлять формами в React-проектах. Одна из них — это formik. Но прежде чем эффективно пользоваться любой библиотекой, нужно потратить некоторое время на её изучение. Часто это приводит к тому, что в маленьких проектах подобные библиотеки использовать просто бессмысленно. В особенности — если над проектом работает команда разработчиков, не все из которых знакомы с некоей библиотекой.
Вот как можно пользоваться этим хуком:
Используя этот хук стоит учитывать одну вещь: надо проконтролировать, чтобы «дорогая» функция не пересоздавалась бы при каждом рендеринге компонента. Дело в том, что это приведёт к сбросу «замедленной» версии этой функции и сотрёт всё из её внутреннего состояния. Обеспечить вышеописанное требование можно двумя путями:
Итоги
Существует немало хороших библиотек, в которых реализованы самые разные хуки. Если вас интересуют подобные библиотеки — можете начать знакомство с ними отсюда. Но, хотя в нашем распоряжении имеется множество полезных пользовательских хуков, хочу отметить, что те пять, о которых я рассказал — это те самые хуки, которые пригодятся в любом React-проекте.
Какими React-хуками вы пользуетесь чаще всего?
Типизированные хуки¶
React api насчитывает десять предопределенных хуков большинство которых являются универсальными. Каждый из них будет рассмотрен по отдельности. Кроме того, данная глава будет посвящена определению пользовательских хуков с учетом последних возможностей TypeScript.
Предопределенные хуки¶
useState ()¶
Это в свою очередь означает, что при отсутствии инициализационного значения (вызов функции useState без значения) или его временном замещении значением принадлежащим к другому типу (например объектный тип замещается значением null ), или частичном значении (объектный тип определяющий лишь часть своих членов), изменить его в будущем с помощью функции обозначаемой dispatch будет невозможно, поскольку она при определении также ассоциируется с выведенным на основе initialState типом.
Все описанные случаи так или иначе предполагают дополнительные проверки на существование значения, которые на практике отягощают код. Поэтому при отсутствии конкретного состояния выступающего в роли аргумента универсальной функции, всегда лучше устанавливать значение по умолчанию в полной мере соответствующего типу, нежели допускать его отсутствие, замещение или частичную установку.
При условии что initialState представлен значением в полной мере соответствующим требуемому типу, необходимость в уточнении с помощью аргумента типа пропадает, поскольку выводу типов не составит особого труда справится самостоятельно. Простыми словами, если аргумент типа не устанавливается, его тип выводится на основе типа к которому принадлежит аргумент вызываемой функции.
В случае, когда состояние в полной мере устанавливается в качестве единственного аргумента хука useState () необходимости в уточнении типа, при помощи аргумента функционального типа, не требуется. Если в качестве значения выступает примитив или объект, все члены которого иниициализированны, вывод типов будет только рад взять работу по установлению типа на себя.
useEffect() и useLayoutEffect()¶
useContext ()¶
Уточнение с помощью аргумента типа может потребоваться только при необходимости приведения более общего типа к более конкретному. Но в реальности универсальная функция этого не позволяет сделать.
При возникновении потребности в подобном приведении конкретизировать необходимо идентификатор ассоциированный со значением, то есть переменную.
useReducer ()¶
Возвращаясь к основному примеру осталось определить компонент в котором и будет происходить определение элементов Redux. Первым делом в теле компонента определим инициализационное состояние которое с помощью функции обозначенной ранее как stateInit будет преобразовано в значение соответствующее типу необходимого редюсеру. Стоит заметить что поскольку инициализационное значение в полной мере соответствует типу InitilState аннотация типа является излишней. При определении с помощью универсальной функции useReducer() элементов редакса стоит сделать акцент на отсутствии необходимости в указании аргументов типа, поскольку вывод типов будет опираться на аннотации типов параметров данного хука.
Осталось рассмотреть только случай предполагающий указания аргументов типа универсальной функции острая необходимость в которых возникнет при определении аргументов в момент вызова. В таком случае в качестве первого аргумента типа универсальная функция ожидает тип описывающий функцию редюсер, вторым инициализационное состояние. Кроме того прибегнуть к помощи аргументов типа может потребоваться и при других сценариях рассматриваемых на всем протяжении темы посвященной хукам. Также не стоит забывать что универсальная функция useReducer имеет множество перегрузок что на практике допускает сценарии отличные от данного примера.
useCallback ()¶
Следующий на очереди универсальный хук useCallback (callback: T, deps): T рассмотрение которого можно ограничить иллюстрацией его применения, поскольку с ним не связанно ничего что к данному моменту могло бы вызвать хоть какой-то вопрос. Другими словами, как и в остальных рассмотренных ранее случаях прибегать к помощи аргументов типа следует только тогда, когда сигнатура функции обозначенной как callback частично или вовсе не имеет указания типов.
useRef ()¶
Следующий хук useRef (initialValue) является универсальной функцией предназначенной для создания объекта рефы. Поскольку объект рефы возвращаемый из данного хука может служить не только для хранения ссылок на React элементы и React компоненты, но и на любые другие значения, которые к тому же могут выступать в качестве единственного аргумента, данная функция имеет множество перегрузок. И кроме этого на текущий момент с ней связан один нюанс. Но обо всем по порядку.
В тех случаях когда объект рефы выполняет роль хранилища для не относящихся к React значений, в его определении нет ничего необычного. Простыми словами, если инициализационное значение устанавливается частично или не устанавливается вовсе, то его тип необходимо конкретизировать с помощью аргумента типа. В противном случае переложить эту работу на вывод типов.
Также стоит обратить внимание, что идентификатор которому в момент определения присваивается результат вызова функции useRef() в явной аннотации не нуждается.
Более подробно с данным хуком можно познакомиться в главе посвященной рефам.
useImperativeHandle ()¶
useMemo ()¶
Следующим на очереди хук useMemo (factory): T является универсальной функцией ожидающей в качестве первого параметра фабричную функцию вычисляющую возвращаемое хуком значение тип которого, при необходимости, можно указать с помощью аргумента типа. Второй обязательный параметр принадлежит к типу массива при наличии и изменении элементов которого происходит повторный вызов фабричной функции.
Как всегда, стоит упомянуть, что явное указание аргумента типа универсальной функции необходимо в очень редких случаях, каждый из которых был рассмотрен на протяжении всей темы посвященной хукам. Кроме того, переменная которой присваивается возвращаемое из хука значение, в аннотации типа и вовсе не нуждается.
useDebugValue ()¶
Следующий и последний на очереди хук useDebugValue (data: T, format(data: T) => any): void предназначенный для вывода логов в devtools является универсальной функцией первый параметр которой ожидает выводимое в консоль значение, чьё форматирование может быть осуществлено при помощи функции выступающей в роли второго необязательного параметра. Вся информация имеющаяся к этому моменту и касающаяся явного указания типов в полной мере справедлива и для текущего хука.
Пользовательский хук¶
Помимо предопределенных хуков рассмотренных ранее, React также позволяет определять пользовательские хуки, которым будет посвящен остаток текущей главы.
Кроме состояния, хук должен предоставлять функции выступающие в роли контролов предназначенных для управления анимацией печатанья. Поэтому вторым шагом потребуется описать тип представляющих контролы что совершенно не составит никакого труда, так как старт\пауза\стоп являются обычными функциями которые ничего не возвращают.
Осталось описать тип представляющий сам хук или если быть точнее, сигнатуры функции, которой по сути он является. Особое внимание стоит обратить на возвращаемый тип представленный размеченным кортежем, для которого несмотря на большое количество кода не был создан псевдоним. Такое решение было принято из-за того, что автокомплит ide указал бы возвращаемый тип как псевдоним, сделав тем самым подсказку малоинформативной, лишив её информации, доступной благодаря меткам.
Осталось лишь определить сам хук.
Поскольку логика работы хука не имеет никакого отношения к TypeScript, её детального разбора не будет. Тем не менее полный код представлен и при желании испытать свои знания, предлагается самостоятельно устно его прокомментировать.
Изучение методов кэширования в React
Как использовать memoization, contexts, useMemo, useState, и useEffect
Для будущих учащихся на курсе «React.js Developer» подготовили перевод материала. Также приглашаем всех желающих на открытый вебинар «ReactJS: быстрый старт. Сильные и слабые стороны».
Сбор данных в React — это одно. Хранение и кэширование этих данных — это другая история. Возможности кажутся бесконечными, а различия часто тонкие, что делает выбор правильной техники иногда немного сложным.
И много анимированных GIF-файлов. Что еще вы можете желать?
Наши данные
Перед тем, как нам углубиться в код, мы можем бегло взглянуть на данные, которые будем извлекать из (большинства) наших компонентов. Файл, который действует как наш API, выглядит следующим образом:
Этот код выполняется, когда мы делаем запрос к пути /api/people в нашем проекте. Как видите, мы вернули объект с двумя свойствами:
randomNumber : случайное число в диапазоне 0-10000.
people : статический массив с тремя вымышленными именами.
Свойство randomNumber поможет нам визуализировать, отображаем ли мы кэшированные данные во фронтенде или нет. Просто продолжайте читать. Скоро это будет понятно.
Компонент People
Когда мы отображаем данные из нашего API, мы передаем их компоненту под названием PeopleRenderer. Это выглядит так:
Учитывая все вышесказанное, давайте посмотрим на первое решение ниже.
useEffect
Внутри наших компонентов мы могли бы использовать хук-эффект useEffect Hook для получения данных. Затем мы можем хранить их локально (внутри компонента), используя useState :
При передаче пустого массива в качестве второго параметра (см. строку 11), useEffect Hook (хук-эффект) будет выполнен, когда наш компонент будет установлен в DOM (Document Object Model) — и только после этого. Он не будет выполняться снова, когда наш компонент будет перезагружен. Это называется «выполнить один раз» Hook (хук).
Ограничение использования useEffect в этом случае заключается в том, что когда мы имеем несколько экземпляров компонента в нашем DOM, все они будут получать данные по отдельности (когда они установлены):
В этом способе нет ничего плохого. Иногда, это именно то, что мы хотим. Но в других случаях нам может понадобиться один раз получить данные и повторно использовать их во всех других случаях путем кэширования. Мы можем использовать несколько способов для достижения этого.
Memoization (Мемоизация)
Memoization — это причудливое слово для очень простой техники. Это означает, что вы создаете функцию, и каждый раз, когда она вызывается заново, она сохраняет результаты выполнения в кэше для предотвращения повторных вычислений.
При первом вызове этой memoized функции результаты рассчитываются (или извлекаются, что бы Вы ни делали внутри тела функции). Перед возвращением результатов вы храните их в кэше под ключом, который создается с входными параметрами:
Создание такого кода шаблона может быстро стать громоздким, поэтому такие популярные библиотеки, как Lodash и Underscore, предоставляют утилиты, которые можно использовать для легкого создания memoized функций:
Использование memoization для получения данных
Теперь мы можем заменить наш useEffect Hook на другой, который выглядит так:
Так как результат getData мемоизуется (memoized), все наши компоненты получат одни и те же данные, когда они будут смонтированы:
Анимация: В наших компонентах используется один и тот же memoized Promise.
Стоит также отметить, что данные уже предварительно собраны, когда мы открываем страницу memoize.tsx (перед тем, как мы смонтируем первую версию нашего компонента). Это потому, что мы определили нашу функцию getData в отдельном файле, который включен в верхнюю часть нашей страницы, и Promise создается при загрузке этого файла.
Мы также можем аннулировать, признать недействительным (пустым) кэш, назначив совершенно новый Cache в качестве свойства cache нашей мемоизованной (memoized) функции:
Как вариант, вы можете очистить существующий кэш (с помощью функции Map):
Но это специфичная функциональность для Lodash. Другие библиотеки требуют иных решений. Здесь вы видите аннулирование кэша в действии на примере:
Анимация: Сброс кэша memoized функции getData.
React Context (React Контекст)
Еще одним популярным и широко обсуждаемым (но часто непонятным) инструментом является React Context. И на заметку, еще раз, он не заменяет такие инструменты, как Redux. Это не инструмент управления состоянием.
Mark Erikson ведет тяжелую битву в Интернете и продолжает объяснять, почему это так. Настоятельно рекомендую прочитать его последнюю статью на эту тему.
И если вам это действительно интересно, прочитайте и мою статью по этой теме:
Так что же такое Context (Контекст)? Это механизм для внесения данных в ваше дерево компонентов. Если у вас есть какие-то данные, вы можете хранить их, например, с помощью useState Hook внутри компонента высоко в иерархии компонентов. Затем вы можете с помощью Context Provider внедрить данные в дерево, после чего вы сможете читать (использовать) данные в любом из компонентов внизу.
Это легче понять на примере. Во-первых, создайте новый контекст:
Затем мы заворачиваем (wrap) тот компонент, который отображает (renders) ваши компоненты People, с помощью Context Provider:
На 12-ой строке мы можем сделать все, что захотим. В какой-то момент, далее вниз по дереву, мы отобразим наш компонент (компоненты) People:
Мы можем использовать значение от Provider с помощью useContext Hook. Результат выглядит следующим образом:
Анимация: Использование данных из Context (Контекста).
Обратите внимание на одну важную разницу! В конце анимации выше мы нажимаем кнопку «Set new seed». При этом данные, которые хранятся в нашем Context Provider, будут заново извлечены. После этого (через 750 мс) вновь полученные данные становятся новым значением нашего Provider, а наши компоненты People перезагружаются. Как видите, все они используют одни и те же данные.
Это большое отличие от примера мемоизации (memoization), который мы рассмотрели выше. В том случае каждый компонент хранил свою копию мемоизуемых (memoized) данных с помощью useState. А в этом случае, используя и потребляя контекст, они не хранят копии, а только используют ссылки на один и тот же объект. Поэтому при обновлении значения в нашем Provider все компоненты обновляются одними и теми же данными.
useMemo
И последнее, но не менее важное, это беглый взгляд на useMemo. Этот Hook отличается от других методов, которые мы уже рассматривали, в том смысле, что это только форма кэширования на локальном уровне: внутри одного элемента компонента. Вы не можете использовать useMemo для обмена данными между несколькими компонентами — по крайней мере, без таких обходных путей, как пробрасывание (prop-drilling) или внедрение зависимостей ((dependency injection) (например, React Context)).
useMemo является инструментом оптимизации. Вы можете использовать его для предотвращения пересчета значения при каждом повторном использовании вашего компонента. Документация описывает это лучше, чем могу я, но давайте рассмотрим пример:
getRnd (строка 2): функция, возвращающая случайное число в диапазоне 0-10000.
Функция выполняется только при изменении значения возраста. Если наш компонент повторно будет вызван (re-rendered) и значение age не изменится, то useMemo просто вернёт мемоизованный (memoized) результат.
В данном примере вычисление pow не очень сложное, но вы можете представить себе преимущества этого, учитывая, что наша функция становится более тяжелой и нам приходится часто визуализировать (re-rendered) наш компонент.
Анимация: Много обращений (re-renders), но значение pow мемоизуется (memoized) с useMemo.
Анимация: Наше мемоизованное (memoized) значение обновляется при обновлении зависимости.
Заключение
Существует множество методик и утилит для кэширования данных в JavaScript. Эта статья дает только поверхностные знания, но я надеюсь, что она содержит некоторые сведения, которые вы захватите с собой на пути своего развития.
Весь программный код, использованный в этой статье, можно найти у меня в архиве на GitLab.