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

Простой математический пример

Функция сокращения полезна для всех видов данных, но мы начнем с рассмотрения очень простого варианта использования: вычисления суммы списка чисел.

Мы можем сделать это функционально, передав функцию, которая добавляет два числа в reduce, который определен в Array.protoype в JavaScript:

Reduce будет продолжать вызывать нашу функцию с парами значений, начиная с начального значения и первого значения из списка. Затем он снова вызовет функцию с результатом этого первого вычисления и следующего элемента из списка и продолжит работу до конца списка.

Таким образом, мы можем сказать, что, учитывая наш список чисел (input), мы хотим получить одно число (sum), применив функцию (add ) для всех чисел в списке - или: мы хотим уменьшить наш список чисел до одного числа. Reduce - это функция высшего порядка, потому что один из ее аргументов сам по себе является функцией, и мы называем этот аргумент функцией reducer.

От элементарной математики до государственного управления

Какое отношение ко всему этому имеет состояние в redux? Давайте мысленно заменим наш список чисел бесконечным потоком действий, которые происходят в нашем приложении, и вместо числа в качестве начального значения мы используем наш объект начального состояния:

Теперь наша функция-редуктор будет вызвана с объектом начального состояния и первым действием («запросить задачи с сервера») и создаст новый объект состояния (который включает «загрузка: истина», но не изменяет остальную часть состояния). . Затем этот объект состояния будет использоваться для создания следующего состояния, комбинируя его со следующим действием: после того, как мы «получили TODO», флаг «загрузка» снова становится ложным, и у нас есть список задач для визуализации вместо пустого массив из начального состояния. Это продолжается до тех пор, пока пользователь взаимодействует с приложением.

В веб-приложениях нам нужно больше, чем конечный результат

Возвращаясь к нашему первоначальному примеру, имеет смысл взглянуть на другую функцию, которая тесно связана с «уменьшить» и обычно называется «сканирование»: она делает то же самое, что и сокращение, только вместо того, чтобы давать нам только конечный результат, она будет также включить все промежуточные результаты. Таким образом, мы можем изменить наш код, чтобы получить список всех промежуточных результатов, просто заменив reduce на scan.

(Обратите внимание, что следующий фрагмент не будет запущен, потому что массивы в JavaScript не имеют функции сканирования. Если вы хотите поиграть с такими примерами функционального программирования, как этот, я предлагаю использовать ramda REPL)

Единственное, что изменилось по сравнению с исходным примером, - это функция, которую мы вызвали с помощью нашей функции добавления - остальной код точно такой же, и мы по-прежнему называем наш аргумент «редуктором».

Обойдя круг и снова подумав о веб-приложениях, мы можем представить, что мы просматриваем список действий и продолжаем выдавать промежуточные состояния, которые затем отражаются в нашем пользовательском интерфейсе. Вот почему redux просит вас реализовать функцию reducer .

TL; DR: Redux просматривает бесконечный поток действий для создания промежуточных состояний, которые отображаются на уровне представления. На это влияет семейство функций функционального программирования, таких как «сокращение» и «сканирование», которые являются функциями высшего порядка, которым вы передаете функцию-редуктор.