Что такое состояние в react
Использование хука состояния
Хуки — нововведение в React 16.8, которое позволяет использовать состояние и другие возможности React без написания классов.
На странице введения в хуки мы познакомились с ними на этом примере:
Давайте начнём изучать хуки, сравнив этот код с эквивалентным кодом на основе класса.
Эквивалентный пример с классом
Если вы уже пользовались классами в React, то вам знаком такой код:
Возможно, вы спросите себя, почему мы используем в качестве примера счётчик, а не что-то более реалистичное. Дело в том, что мы хотим обратить ваше внимание на API, одновременно делая первые шаги с хуками.
Хуки и функциональные компоненты
Напоминаем, что функциональные компоненты в React выглядят так:
Возможно, вы слышали, что такие компоненты называются «компонентами без состояния». Сейчас мы покажем, как использовать внутри них состояние React, поэтому будем называть их «функциональными компонентами».
Хуки НЕ работают внутри классов, а используются вместо них.
Наш новый пример начинается с того, что импортирует хук useState из React:
Что такое хук? Хук — это специальная функция, которая позволяет «подцепиться» к возможностям React. Например, хук useState предоставляет функциональным компонентам доступ к состоянию React. Мы узнаем про другие хуки чуть позже.
Когда применить хук? Раньше, если вы писали функциональный компонент и осознавали, что вам нужно наделить его состоянием, вам приходилось превращать этот компонент в класс. Теперь же вы можете использовать хук внутри существующего функционального компонента. Мы покажем это прямо сейчас!
Есть специальные правила о том, где можно, а где нельзя использовать хуки внутри компонента. Их мы изучим в главе Правила хуков.
Объявление переменной состояния
Слово «create» («создать») было бы не совсем точно, потому что состояние создаётся только в момент, когда компонент рендерится впервые. В последующие же рендеринги useState возвращает текущее состояние. Иначе не существовало бы «состояния» как такового. Названия всех хуков начинаются с «use» тоже неспроста. О причине мы узнаем из Правил хуков.
Когда мы хотим отобразить текущее состояние счётчика в классе, мы обращаемся к this.state.count :
В функции же мы можем использовать count напрямую:
Давайте построчно пробежимся по тому, что мы выучили и проверим наши знания:
Поначалу это всё может показаться слишком сложным. Не торопитесь! Если вы запутались в объяснении, ещё раз прочитайте приведённый код с начала до конца. Обещаем, если вы на минутку «забудете», как состояние работает в классах, и посмотрите на код свежим взглядом, всё станет ясно.
Совет: Что делают квадратные скобки?
Вы могли обратить внимание на квадратные скобки в месте, где объявляется переменная состояния:
Два имени в квадратных скобках не содержатся в API React. Названия переменным состояния выбираете вы:
Совет: Использование нескольких переменных состояния
Объявлять переменные состояния через пару [something, setSomething] удобно ещё и тем, что когда нам нужны несколько переменных, мы можем назвать каждую из них собственным именем:
Использовать несколько переменных состояния совсем не обязательно, потому что они могут быть объектами или массивами, которые группируют связанные по смыслу данные. Обратите внимание, что, в отличие от this.setState в классах, обновление переменной состояния всегда замещает её значение, а не осуществляет слияние.
Подробные рекомендации о разделении независимых переменных состояния вы найдёте в FAQ.
А теперь давайте перейдём к изучению хука useEffect , похожего на методы жизненного цикла в классах. С его помощью компоненты могут выполнять побочные эффекты.
2.6 Состояние и жизненный цикл
В этом разделе мы расскажем о таких важных концепциях, как состояние и жизненный цикл компонента React. Более подробный API компонента вы можете найти здесь.
Рассмотрим, упомянутый ранее, пример тикающих часов.
Пока что мы знаем только один способ обновления UI.
В этом разделе мы сделаем компонент Timer по-настоящему переиспользуемым и инкапсулированным. Он сначала установит собственный таймер, а затем станет периодически обновляться через определенный промежуток времени.
Давайте начнём с инкапсуляции кода в компонент Timer :
В идеале, нам необходимо спроектировать самообновляющийся компонент Timer так, чтобы код, который его использует имел следующий вид:
Чтобы этого добиться, к компоненту Timer нужно добавить состояние.
Состояние похоже на свойства props, однако является приватным и полностью контролируется компонентом.
Раньше состоянием могли обладать только компоненты-классы. Однако с появлением хуков состоянием могут обладать и компоненты-функции.
2.6.1 Преобразование функций в классы
Мы можем преобразовать компонент-функцию Timer в класс за пять шагов:
Теперь компонент Timer определён как класс, а не как функция.
2.6.2 Добавление локального состояния в класс
Давайте переместим date из props в state в три этапа.
1. Заменим this.props.value на this.state.value в методе render() :
2. Добавим конструктор класса, который устанавливает начальное состояние this.state :
Обратите внимание на то, как мы передаем свойства props в базовый конструктор:
3. Удаляем свойство value из элемента:
Позже мы добавим код таймера обратно в сам компонент.
Результат будет выглядеть следующим образом:
Далее мы сделаем так, что компонент Timer будет устанавливать таймер и обновлять себя каждую секунду.
2.6.3 Добавление методов жизненного цикла в класс
При старте приложения React, компонент Timer будет впервые отрисован в DOM. В React это называется монтированием/монтажом компонента.
После каждого монтирования Timer ему нужно устанавливать таймер, чтобы периодически себя обновлять.
Однако в приложениях с множеством компонентов очень важно высвобождать ресурсы, занятые компонентами, когда они уничтожаются, чтобы избежать утечек памяти.
React позволяет объявить в компоненте-классе специальные методы, чтобы запускать определенный код, когда компонент монтируется или демонтируется:
В документации эти методы называются «lifecycle hooks». Мы же для простоты будем называть их методами жизненного цикла (ЖЦ).
Мы будем очищать таймер в методе жизненного цикла componentWillUnmount() :
Теперь компонент постоянно обновляется через установленный промежуток времени.
Давайте подытожим всё, что произошло, а также порядок, в котором вызываются методы:
Когда результат отрисовки Timer вставлен в DOM, React вызывает метод componentDidMount() жизненного цикла. Внутри него компонент Timer обращается к браузеру для установки таймера, чтобы вызывать increment() раз в секунду.
Если компонент Timer в какой-то момент удалён из DOM, React вызывает метод componentWillUnmount() жизненного цикла, из-за чего таймер останавливается.
2.6.4 Корректное обновление состояния
О setState() нужно знать три вещи.
2.6.4.1 Не модифицируйте состояние напрямую
К примеру, этот компонент перерисовываться не будет:
Для корректной модификации состояния компонента используйте метод setState() :
Вы можете установить this.state только в конструкторе!
2.6.4.2 Обновления состояния могут быть асинхронными
React может собирать последовательность вызовов setState() в единое обновление в целях повышения производительности.
Так как React может обновлять this.props и this.state асинхронно, вы не должны полагаться на их значения для вычисления следующего состояния.
К примеру, такой код может не обновить температуру:
Мы использовали стрелочную функцию, но можно использовать и обычные функции:
2.6.4.3 Обновления состояния объединяются
Например, состояние вашего компонента может содержать множество независимых переменных:
Далее вы можете обновить их независимо с помощью отдельных вызовов setState() :
2.6.5 Нисходящий поток данных
Вот почему состояние часто называют локальным или инкапсулированным. Оно недоступно для какого-либо компонента, за исключением того, который им владеет и устанавливает.
Компонент может решить передать это состояние вниз как свойства props своим дочерним компонентам:
Таким же образом это работает и для пользовательских компонентов:
Это принято называть «сверху-вниз», «нисходящим» или однонаправленным потоком данных. Любое состояние всегда находится во владении какого-либо компонента. Любые данные или UI, производные от этого состояния могут передаваться только в компоненты «ниже» их в дереве иерархии.
Если представить дерево компонентов как «водопад» свойств, то состояние каждого компонента является подобием дополнительного источника воды, который соединяется с водопадом в произвольной точке и также течет вниз.
Каждый компонент устанавливает своё собственное значение и обновляется независимо.
В приложениях React, независимо от того, обладает ли компонент состоянием – состояние является деталью реализации этого компонента и может изменяться со временем. Вы можете использовать компоненты без состояния внутри компонентов, имеющих состояние, и наоборот.
React State с нуля
Дата публикации: 2018-04-10
От автора: как только вы приступите к изучению React, вы столкнетесь с понятием state. State имеет огромное значение в React. Возможно, это первая причина, почему вы стали изучать React. Давайте рассмотрим понятие React state и принцип его работы.
Что такое state?
State (состояние) в React – это объект простого JS, позволяющий отслеживать данные компонента. Состояние компонента может меняться. Смена состояния компонента зависит от функциональности приложения. Изменения могут основываться на ответе от пользователя, новых сообщениях с сервера, ответа сети и т.д.
Состояние компонента должно быть приватным для компонента и контролироваться им. Изменения состояния компонента необходимо делать внутри компонента – инициализация и обновление состояния компонента.
Компоненты класса
Состояния доступны только для компонентов класса. Главная причина, почему вы захотите использовать компоненты класса, а не функциональные компоненты заключается в том, что компоненты класса могут обладать состоянием. Давайте разберемся, в чем разница. Функциональные компоненты – это JS функции:
Изучите основы ReactJS на практическом примере по созданию учебного веб-приложения
Состояние и жизненный цикл
На этой странице представлена концепция состояния и жизненного цикла в компоненте React. Здесь вы можете найти подробный справочник API компонента.
Рассмотрим пример тикающих часов из одного из предыдущих разделов. В разделе Отрисовка элементов мы изучили только один способ обновления пользовательского интерфейса (UI). Мы вызываем ReactDOM.render() для изменения отрисованного вывода:
В этом разделе мы узнаем, как сделать компонент Clock действительно повторно используемым и инкапсулированным. Его можно будет настроить и он будет обновлять самого себя каждую секунду.
Мы можем начать с инкапсуляции кода в функциональный компонент часов:
В идеале мы хотим написать это один раз и иметь само обновление Clock :
Состояние похоже на свойство, но оно является закрытым и полностью контролируется компонентом.
Мы упоминали ранее, что компоненты, определённые как классы, имеют некоторые дополнительные возможности. Локальное состояние — это как раз одно из них: эта возможность доступна только классам.
Преобразование функции в класс
Удалить оставшиеся пустое объявление функции.
Clock теперь определён как класс, а не функция.
Метод render будет вызываться каждый раз, когда происходит обновление, но пока мы отрисовываем в один и тот же DOM-узел, только один экземпляр класса Clock будет использоваться. Это позволяет использовать дополнительные возможности, такие как локальное состояние и хуки жизненного цикла.
Добавление локального состояния в класс
Мы переместим date из свойств в состояние за три шага:
Обратите внимание, что мы передаём props базовому (родительскому) конструктору:
Позже мы добавим код таймера обратно к самому компоненту.
Результат выглядит следующим образом:
Затем мы позволим настроить Clock собственным таймером с обновлением каждую секунду.
Добавление методов жизненного цикла в класс
В приложениях с множеством используемых компонентов очень важно освобождать ресурсы, занятые при их удалении.
Мы хотим настроить таймер всякий раз, когда Clock отрисовывается в DOM в первый раз. Это называется «монтированием» (установкой) в React.
Мы можем объявить специальные методы в классе-компоненте для выполнения кода, когда компонент устанавливается и удаляется:
Эти методы называются “хуками (методами) жизненного цикла”.
Хук componentDidMount() запускается после того, как вывод компонента отрисован в DOM. Это хорошее место для установки таймера:
Хотя this.props настраивается самим React, и у this.state есть специальное значение, вы можете добавлять дополнительные поля в класс вручную, если вам нужно сохранить что-то, что не участвует в при выводе данных (например, идентификатор таймера).
Мы удалим таймер в хуке жизненного цикла componentWillUnmount() :
Он будет использовать this.setState() для планирования обновлений локального состояния компонента:
Теперь часы тикают каждую секунду.
Давайте быстро повторим, что происходит, а также перечислим порядок, в котором вызываются методы:
Правильное использование состояния
Не изменяйте напрямую состояние
Например, это не приведёт к повторной отрисовке компонента:
Вместо этого используйте setState() :
Обновления состояния могут быть асинхронными
React может выполнять несколько вызовов setState() за одно обновление для лучшей производительности.
Поскольку this.props и this.state могут обновляться асинхронно, вы не должны полагаться на их значения для вычисления следующего состояния.
Например, этот код может не обновить счётчик:
Мы использовали стрелочную функцию выше, но это также работает с обычными функциями:
Обновления состояния объединяются
Например, ваше состояние может содержать несколько независимых переменных:
Затем вы можете самостоятельно их обновлять с помощью отдельных вызовов setState() :
Однонаправленный поток данных
Ни родительский, ни дочерний компоненты не могут знать, является ли какой-либо компонент с состоянием или без него, и им не важно, определен ли он как функция или класс.
Вот почему состояние часто называют локальным или инкапсулированным. Оно недоступно для любого компонента, за исключением того, который владеет и устанавливает его.
Компонент может передать своё состояние вниз по дереву компонентов в виде свойства его дочерних компонентов:
Это также работает для пользовательских компонентов:
Это обычно называют потоком данных «сверху вниз» или «однонаправленным потоком данных». Любое состояние всегда принадлежит определённому компоненту, а любые данные или пользовательский интерфейс, полученные из этого состояния, могут влиять только на компоненты, находящиеся «ниже» в их дереве компонентов.
Если вы представляете дерево компонентов как водопад свойств, состояние каждого компонента похоже на дополнительный источник воды, который соединяется с водопадом в произвольной точке, но также течёт вниз.
В React-приложениях, независимо от того, является ли компонент, имеющим состояние или нет, — это рассматривается как деталь реализации компонента, которая может измениться со временем. Вы можете использовать компоненты без состояния в компонентах с состоянием, и наоборот.
Состояние и жизненный цикл
На этой странице представлены понятия «состояние» (state) и «жизненный цикл» (lifecycle) React-компонентов. Подробный справочник API компонентов находится по этой ссылке.
В качестве примера рассмотрим идущие часы из предыдущего раздела. В главе Рендеринг элементов мы научились обновлять UI только одним способом — вызовом ReactDOM.render() :
Для начала, извлечём компонент, показывающий время:
В идеале мы бы хотели реализовать Clock таким образом, чтобы компонент сам себя обновлял:
«Состояние» очень похоже на уже знакомые нам пропсы, отличие в том, что состояние контролируется и доступно только конкретному компоненту.
Преобразование функционального компонента в классовый
Давайте преобразуем функциональный компонент Clock в классовый компонент за 5 шагов:
Теперь Clock определён как класс, а не функция.
Метод render будет вызываться каждый раз, когда происходит обновление. Так как мы рендерим в один и тот же DOM-контейнер, мы используем единственный экземпляр класса Clock — поэтому мы можем задействовать внутреннее состояние и методы жизненного цикла.
Добавим внутреннее состояние в класс
Переместим date из пропсов в состояние в три этапа:
Обратите внимание, что мы передаём props базовому (родительскому) конструктору:
Позже мы вернём код таймера обратно и на этот раз поместим его в сам компонент.
Результат выглядит следующим образом:
Теперь осталось только установить собственный таймер внутри Clock и обновлять компонент каждую секунду.
Добавим методы жизненного цикла в класс
В приложениях со множеством компонентов очень важно освобождать используемые системные ресурсы, когда компоненты удаляются.
Первоначальный рендеринг компонента в DOM называется «монтирование» (mounting). Нам нужно устанавливать таймер всякий раз, когда это происходит.
Каждый раз когда DOM-узел, созданный компонентом, удаляется, происходит «размонтирование» (unmounting). Чтобы избежать утечки ресурсов, мы будем сбрасывать таймер при каждом «размонтировании».
Объявим специальные методы, которые компонент будет вызывать при монтировании и размонтировании:
Эти методы называются «методами жизненного цикла» (lifecycle methods).
Метод componentDidMount() запускается после того, как компонент отрендерился в DOM — здесь мы и установим таймер:
Обратите внимание, что мы сохраняем ID таймера в this ( this.timerID ).
Поля this.props и this.state в классах — особенные, и их устанавливает сам React. Вы можете вручную добавить новые поля, если компоненту нужно хранить дополнительную информацию (например, ID таймера).
Теперь нам осталось сбросить таймер в методе жизненного цикла componentWillUnmount() :
this.setState() планирует обновление внутреннего состояния компонента:
Теперь часы обновляются каждую секунду.
Давайте рассмотрим наше решение и разберём порядок, в котором вызываются методы:
Как правильно использовать состояние
Не изменяйте состояние напрямую
В следующем примере повторного рендера не происходит:
Вместо этого используйте setState() :
Конструктор — это единственное место, где вы можете присвоить значение this.state напрямую.
Обновления состояния могут быть асинхронными
React может сгруппировать несколько вызовов setState() в одно обновление для улучшения производительности.
Поскольку this.props и this.state могут обновляться асинхронно, вы не должны полагаться на их текущее значение для вычисления следующего состояния.
Например, следующий код может не обновить счётчик:
В данном примере мы использовали стрелочную функцию, но можно использовать и обычные функции:
Обновления состояния объединяются
Например, состояние может состоять из нескольких независимых полей:
Их можно обновлять по отдельности с помощью отдельных вызовов setState() :
Однонаправленный поток данных
В иерархии компонентов ни родительский, ни дочерние компоненты не знают, задано ли состояние другого компонента. Также не важно, как был создан определённый компонент — с помощью функции или с помощью класса.
Состояние часто называют «локальным», «внутренним» или инкапсулированным. Оно доступно только для самого компонента и скрыто от других.
Компонент может передать своё состояние вниз по дереву в виде пропсов дочерних компонентов:
Это, в общем, называется «нисходящим» («top-down») или «однонаправленным» («unidirectional») потоком данных. Состояние всегда принадлежит определённому компоненту, а любые производные этого состояния могут влиять только на компоненты, находящиеся «ниже» в дереве компонентов.
Если представить иерархию компонентов как водопад пропсов, то состояние каждого компонента похоже на дополнительный источник, который сливается с водопадом в произвольной точке, но также течёт вниз.
У каждого компонента Clock есть собственное состояние таймера, которое обновляется независимо от других компонентов.
В React-приложениях, имеет ли компонент состояние или нет — это внутренняя деталь реализации компонента, которая может меняться со временем. Можно использовать компоненты без состояния в компонентах с состоянием, и наоборот.