Если вы занимаетесь разработкой React или React Native, новичок вы или эксперт, который перешел от классов к функциональным компонентам, скорее всего, вы уже знакомы с хуком useState.

Это, вероятно, самый важный хук в наборе инструментов разработчика React, и он используется по крайней мере в половине всех встроенных компонентов по уважительной причине. С помощью этого хука вы можете управлять состоянием компонента и, вместе с реквизитами, контролировать, как и когда компонент будет отображаться.

Мы используем useState во всей нашей кодовой базе, и он никогда нас не подводит. Но иногда нам нужен лучший способ. Это действительно зависит от ситуации. Вы, вероятно, в порядке, если у вас есть один или два экземпляра useState в вашем компоненте. Но код может стать трудным для чтения и понимания, если у вас их три и более.

Например, представьте себе компонент, который обрабатывает набор входных данных, значения которых необходимо собирать и сохранять (например, экран настроек). Когда у вас есть несколько хуков useState, отслеживание того, что происходит, может быстро привести к путанице.

Итак, каково решение? Один из вариантов — использовать пользовательский хук для более организованной обработки нескольких состояний. Это может сделать ваш код немного проще для понимания и поддержки. С помощью пользовательского хука вы можете группировать связанные переменные состояния и их функции обновления, уменьшая беспорядок в ваших компонентах, но в нашем случае мы просто переместили проблему в другое место. Кроме того, пользовательский хук должен будет возвращать отдельные состояния и необходимые методы для их обновления. Не так уж полезно.

Эй, мы можем сделать лучше, чем это? Абсолютно! И угадайте, что мы можем сделать это без использования типичного подхода Redux, который включает диспетчеризацию действий. Вместо этого мы можем использовать менее известный хук под названием useReducer, который легко доступен в React. Но подождите, вам может быть интересно, чем этот хук проще старого проверенного useState?

Хорошо, позвольте мне объяснить. Сначала давайте посмотрим, что делает useReducer, а затем определим, может ли это нам помочь. Согласно Документации React, useReducer — это не основной хук, а скорее дополнительный, а значит, нам не нужно слишком беспокоиться об этом. В документации говорится, что useReducer — это React Hook, который позволяет вам добавить редьюсер к вашему компоненту. И дальше говорится…

Альтернатива useState. Принимает редьюсер типа (state, action) => newState и возвращает текущее состояние в паре с методом отправки. (Если вы знакомы с Redux, вы уже знаете, как это работает.)

useReducer обычно предпочтительнее useState, если у вас сложная логика состояния, включающая несколько подзначений, или когда следующее состояние зависит от предыдущего. useReducer также позволяет оптимизировать производительность компонентов, запускающих глубокие обновления, поскольку вы можете передавать dispatch вместо обратных вызовов.

Но что, если наша логика состояния проста и наше следующее обновление состояния влияет только на пользовательский интерфейс? Можем ли мы по-прежнему использовать useReducer? Давайте посмотрим, что мы можем с этим сделать. Из документации мы можем увидеть, как выглядит код.

Теперь нам нужно выяснить две вещи: как работают dispatch и reducer. Давайте посмотрим.

Как работает рассылка?

Вызывая эту функцию dispatch, вы можете легко обновить состояние до нового значения, что затем вызовет повторную визуализацию компонента. Для этого вам нужно передать один аргумент функции dispatch, называемой действием. Теперь это становится немного запутанным, но давайте посмотрим, что это за действие. Обычно мы видели этот паттерн при использовании диспетчеризации:

Действие — это просто объект, который мы передаем методу reducer при вызове dispatch. Что произойдет, если это действие есть не что иное, как состояние или, еще лучше, частичное состояние? Что ж, это состояние или частичное состояние — это то, что мы собираемся получить в методе обратного вызова reducer. Давайте помнить об этом, когда будем собирать все это вместе.

Как работает редуктор?

Метод обратного вызова reducer указывает, как будет обновляться состояние. Он получает текущее состояние и действие в качестве аргументов и должен возвращать обновленное состояние. Этот метод должен быть чистым в том смысле, что он будет возвращать только что обновленное состояние, созданное из аргументов и без каких-либо побочных эффектов. Для нас важнее всего то, что аргументы состояния и действия могут быть любого типа. Это означает, что возможный полезный редуктор для нас может быть:

Этот редьюсер обновит состояние, объединив старое состояние с новым состоянием, которое было передано как действие. Отлично.

Собираем все вместе

Давайте теперь попробуем переписать весь хук useReducer так, как он подходит для наших нужд.

Теперь вы начинаете видеть, куда я иду, не так ли? Давайте вернемся к исходному примеру и создадим файл initialState.

Когда initialState готово, как мы будем его использовать? Это так просто:

Честно говоря, я думаю, что мне больше нравится этот новый способ. Это напоминает мне старые добрые времена, когда мы все использовали классы и у нас был только один метод для обновления состояния, setState, а локальное состояние было одним большим счастливым объектом. Если это имело смысл тогда, это имеет смысл и сегодня.

Не говоря уже о том, что вы можете обновить состояние более чем одним частичным, как это, что происходит так естественно.

Это здорово, не так ли? Теперь у нас есть один унифицированный способ обновления локального состояния, и если мы хотим добавить новый элемент в объект состояния, нам не нужно задаваться вопросом об имени соответствующего метода для его обновления. Мы будем продолжать использовать setState.

Давайте превратим это в пользовательский хук, который мы сохраним в файле custom-hooks, а затем импортируем его по всему нашему проекту.

И использовать его таким образом.

Хорошая часть, если вы используете обычный JavaScript, заключается в том, что вы можете вызывать setState с любым объектом, тем самым добавляя его к локальному состоянию, даже если он не присутствует в исходном initialState.

Бонус: машинопись

Все выглядит великолепно в старом добром JavaScript, но мы все живем в мире, где TypeScript берет верх. Например, последняя (0.71) версия ReactNative по умолчанию использует TypeScript. Как мы можем получить новый пользовательский хук useSuperState в TypeScript? Давайте посмотрим.

Но этого недостаточно. Вам также необходимо ввести initialState элементов, а также сделать их необязательными, поскольку мы должны иметь возможность устанавливать их по отдельности. Использование Partial на typeof в большинстве случаев поможет.

Подсказка: если у вас изначально undefined членов, вы все равно должны добавить их в initialState и ввести их соответствующим образом, чтобы использовать их безопасно.

Заключение

Нам всем нравится использовать useState в наших приложениях React и React Native, верно? Это просто и выполняет работу большую часть времени.

Но давайте смотреть правде в глаза, когда мы начинаем чрезмерно использовать его, чтение и понимание кода может стать настоящей головной болью.

Даже перенос его на пользовательский хук не всегда проясняет ситуацию. Вот где useReducer приходит на помощь! У него может быть плохая репутация из-за нашего коллективного презрения к Redux, но он может спасти жизнь, когда дело доходит до управления сложной логикой состояния или несколькими переменными состояния.

Кроме того, мы можем даже превратить его в собственный хук с именем, которое больше подходит для нашего конкретного случая использования (например, useSuperState).

Использование useReducer может сделать наш код более легким для чтения и понимания, сохраняя вместе все связанные переменные состояния. Так что попробуйте и посмотрите, как это работает для вас!

Я надеюсь, что вы нашли эту статью полезной, и я хотел бы услышать ваши мысли в комментариях. А если вам понравилось, поддержите его аплодисментами или поделитесь им со своими друзьями. Спасибо за чтение, и следите за обновлениями для большего количества отличного контента!