Это вторая публикация в серии, посвященной моим знаниям при создании приложения React. Смотрите первый пост.
Установка некоторого контекста
Проект, над которым я работаю, представляет собой одностраничное приложение (SPA), с помощью которого пользователи будут создавать и управлять данными с множеством сложных правил проверки. Это внутренний бизнес-инструмент, а не общедоступный веб-сайт.
По этой причине я собираюсь полностью игнорировать «изоморфизм» - то есть сайт, на котором отображение HTML может происходить как на сервере (в узле), так и в браузере. Возможно, вы захотите сделать это для пользовательского сайта / приложения, где время начальной загрузки страницы имеет решающее значение. Для меня пока это не так.
Я также игнорирую React Native и любые проблемы, связанные с запуском в качестве нативного приложения. Я верю команде React, что смогу «научиться один раз, писать где угодно».
Сообщество React, похоже, действительно следует философии YAGNI. Фактически, Лэнс Харпер прокомментировал мой первый пост, что я должен как можно глубже изучить простой React, прежде чем добавлять Redux или что-то еще. Мне нравится такой подход.
Итак, почему я сразу перехожу к Redux в этом посте?
Потому что я не пытаюсь сделать пошаговое руководство или учебное пособие. Я пытаюсь описать - постфактум - причины, по которым я решил или не решил использовать разные библиотеки в моем приложении React. И указать, где я до сих пор не уверен. Как я уже говорил ранее, я уверен, что мое мышление изменится по мере того, как я продолжу учиться.
Учитывая это, я предполагаю, что вы хорошо разбираетесь в React. Если у вас нет такого опыта, я рекомендую:
- Прочитав React docs и следуя руководству.
- Провести несколько часов на курсе Pluralsight Cory House по теме React and Flux. (Есть сотни сообщений и видео об изучении React. Я считаю, что Кори отлично.)
Шаблон потока
Позже в этом посте я порекомендую Redux. Но сначала я собираюсь быстро описать паттерн Flux. Redux - это реализация Flux. Есть и другие реализации - одна из них, разработанная командой React, называется Flux. Это сбивает с толку, но помните, что в этом разделе я описываю шаблон проектирования.
Также имейте в виду, что между React и Flux / Redux нет зависимостей ни в одном направлении. Они выросли из одних и тех же принципов дизайна и поэтому хорошо сочетаются друг с другом, но вы можете использовать любой из них отдельно.
Официальное описание Flux хорошее. Рекомендую прочитать. Вот мой вывод:
Шаблон Flux описывает поток данных в приложении. Золотое правило: данные передаются только в одном направлении. Все изменения в состоянии вашего приложения выполняются в одном месте, а затем ваши представления уведомляются об изменениях.
Основными участниками паттерна Flux являются:
Магазины
Магазин - это единственный источник достоверной информации о состоянии вашего приложения: ваши фактические данные, текущее состояние пользовательского интерфейса и все остальное. Это источник истины, из которого проистекают все изменения. Flux допускает создание нескольких хранилищ, каждое из которых отвечает за один домен (в смысле DDD).
Действия
Действие создается где-то в вашем приложении, например, в представлении, когда пользователь нажимает кнопку. Вы можете думать о действии как о событии или объекте сообщения - он описывает что-то, что произошло, или команду / запрос на то, чтобы что-то произошло.
Действие - это простой объект, который по соглашению имеет свойство type. У него могут быть другие свойства, содержащие соответствующие данные. Например:
{ type: ‘CREATE_COMMENT’, text: ‘React is cool, but so many choices to make!’, author: ‘John’ }
Диспетчер
Затем действие передается единственному глобальному диспетчеру. Диспетчер, в свою очередь, передает действие каждому хранилищу, чтобы обрабатывать его так, как он хочет. В приведенном выше примере хранилище, владеющее комментариями, вероятно, создаст новый объект для представления комментария и добавит его в список комментариев к публикации. Другие магазины могут полностью проигнорировать это.
Каждый раз, когда магазин меняет принадлежащее ему состояние, он публикует уведомление. Ваши представления могут подписаться на эти уведомления, чтобы обновить пользовательский интерфейс для пользователя.
Используя Flux, вы обычно инкапсулируете создание действия в функции, которая затем передает действие глобальному диспетчеру.
function createComment(text, author) { Dispatcher.dispatch({ type: ‘CREATE_COMMENT’, text: text, author: author }) }
Такие функции, как createComment (), должны быть единственным способом изменить состояние вашего приложения. Вы можете поместить любые представления / пользовательский интерфейс поверх них - например, дерево компонентов React.
Подведение итогов паттерна Flux
Таким образом, шаблон Flux дает вам односторонний поток данных:
action -> dispatcher -> store -> view
Действия пользователя в ваших представлениях часто являются триггером для создания и отправки действий, поэтому на самом деле поток:
view -> action -> dispatcher -> store -> view
Да, это происходит по кругу - но только в одном направлении! Важно то, что просмотры никогда не обновляют напрямую состояние приложения в магазине. Только отправленные действия приводят к изменениям состояния.
React основан на идее, что ваш пользовательский интерфейс должен быть исключительно функцией состояния приложения - состояние на входе и выходе - поэтому React и шаблон Flux идут рука об руку.
Реализация Flux
Когда я впервые прочитал о шаблоне Flux, я почувствовал себя как дома. В конце 2000-х я потратил несколько лет на создание системы, в которой все межпроцессное взаимодействие происходило асинхронно на шине сообщений pub / sub (на стороне сервера). Односторонние командные сообщения типа «запустил и забыл», которые приводят к некоторым более поздним сообщениям о событиях, говорящих «что-то изменилось», являются для меня очень удобной концепцией.
Но когда я начал смотреть на реализацию Flux, я был немного сбит с толку. Я понимаю, что у меня несколько магазинов - каждому принадлежит одна часть штата.
Но что это за метод waitFor ()? Магазин вызывает waitFor (), потому что знает, что сначала нужно обновить какое-то другое хранилище? Разве это не нарушает правило, согласно которому каждый магазин независимо владеет своим штатом? Зачем нужны зависимости? И действительно, что со всеми операторами switch? :)
Все это казалось отвратительным. Первым моим порывом было создать собственную реализацию паттерна Flux. (Видите, я действительно становлюсь разработчиком JavaScript!)
Redux спешит на помощь
К счастью, Redux пришел на помощь, прежде чем я очень далеко продвинулся по этому пути.
Сайт Redux великолепен. Я настоятельно рекомендую прочитать вступление и посмотреть видеоролики Начало работы с Redux (всего около 2 часов), снятые создателем Redux Дэном Абрамовым. Они того стоят.
Еще раз: Курс Pluralsight по React и Redux Cory House также является отличным ресурсом.
Что мне нравится в Redux:
Действия по-прежнему представлены в виде простых объектов, и есть одноэлементный диспетчер. Вы взаимодействуете с ним исключительно с помощью метода dispatch ().
Redux поощряет разделение создания объектов действий от их отправки. По соглашению вы пишете функции создания действия, которые создают и возвращают объект действия.
Вы все равно можете написать функцию createComment (), как указано выше, но с Redux она просто возвращает действие. Почему? Иногда создание экшена не так тривиально, как мой пример. Таким образом, вы можете протестировать создание действия независимо от его отправки. Итак, ваш код будет выглядеть так:
dispatch(createComment(‘Testing FTW!’, ‘John’))
В Redux есть только одно хранилище, в котором хранится все состояние. Это простой объект JS. (Или нет. Подробнее об этом в будущих публикациях.)
Если у Flux несколько хранилищ, Redux разделяет ответственность за обновление частей - или поддеревьев - одного хранилища. Например, если ваше состояние для блога выглядело так:
{ user: { isAuthenticated: true, username: ‘john’ }, displayPrefs: { theme: ‘dark’, openLinksInNewTab: true }, blogPosts: [ { id: 1, title: ‘Spelunking in the React ecosystem’, comments: [ { id: 52, value: 'Nice post!', author: 'Lance' } ] }, { id: 2, title: ‘Redux to the rescue!’ }, ] }
У вас будет три редуктора верхнего уровня, по одному соответствующему каждому свойству верхнего уровня в состоянии: user, displayPrefs и blogPosts. Каждый сможет видеть только свою часть дерева состояний. Редукторы также могут быть вложенными. В этом примере у вас, вероятно, будет отдельный редуктор, отвечающий за комментарии. Редуктор blogPosts будет делегировать редуктору комментариев, передавая ему только список комментариев.
Редуктор - это просто функция, которая принимает текущее состояние и действие и возвращает новое состояние. Редукторы не изменяют состояние. Эта неизменяемость помогает избежать ошибок и упрощает тестирование редукторов.
myReducer = function(state, action) { // … do stuff return newState // the original state is unmodified }
Неизменность состояния также помогает Redux иметь отличные инструменты разработки. Вы можете запустить их как расширение Chrome или включить прямо в свой проект с помощью пакета redux-devtools. Они позволяют вам видеть каждое отправленное действие и результирующие изменения состояния (если таковые имеются). Вы даже можете путешествовать во времени - посмотреть, как бы выглядело ваше приложение, если бы одно или несколько из этих действий не произошло.
Redux поощряет передовой опыт React по созданию большинства компонентов чистыми функциями и передаче состояния вниз по дереву компонентов в качестве свойств. Когда вам действительно нужен компонент в середине вашей иерархии, чтобы реагировать на изменения состояния, пакет react-redux предоставляет как раз то, что вам нужно, очень красивым и понятным способом.
Redux прост и понятен, упрощает тестирование и имеет большой опыт разработки. Поток данных по-прежнему:
view -> action -> dispatcher -> reducer -> view
Что не нравится?
Что ж, в редукторах все еще есть все эти инструкции switch для типов действий. Redux выбирает немного шаблонного кода, чтобы не быть слишком волшебным. Это здорово для ясности, и вы можете легко добавить немного волшебства, если не хотите использовать switch.
Скоро…
По мере того, как ваше приложение на основе Redux становится все сложнее, вы, вероятно, столкнетесь с новыми вариантами и вопросами:
- Должен ли я использовать для своего состояния такой пакет, как immutable или icepick, или просто позволить всем не изменять его напрямую?
- Как мне структурировать свои объекты действий?
- Как мне работать с пользовательским вводом и формами? Должны ли все изменения состояния ввода - буквально каждое нажатие клавиши - проходить через Redux?
Я отвечу на эти вопросы в следующих публикациях.