Начальное состояние Redux, полное нулевых значений в Angular

Я работаю над проектом уже около 10 месяцев, который основан на Angular 4+ и Redux (через angular-redux/store). Этот проект был в основном успешным, он запущен в производство с января и не испытывает каких-либо серьезных проблем.

Но... Я постепенно разочаровывался в Redux по одной проблеме, которую я считаю по крайней мере раздражающей, но потенциально плохой для обслуживания: начальное состояние со значениями null, поэтому я хотел бы выяснить, что я я делаю неправильно - или если это проблема дизайна Redux как такового.

Как это воспроизвести

Вот простой и хороший пример этого: https://github.com/marinho/example-app< /а>

Вы просто клонируете, npm install его, затем запускаете npm run ng serve и загружаете localhost:4200 в браузере. Затем перейдите в Консоль и отфильтруйте по 111, чтобы увидеть две строки:

11111 undefined
11112 null

Это напечатано двумя наблюдаемыми подписками, которые соответственно отслеживают значения на myUndefined и myObjects.country. Первый является недопустимым ключом (не был инициализирован в глобальном состоянии), второй был инициализирован с помощью null.

Более точное расположение этих подписок находится здесь: https://github.com/marinho/example-app/blob/master/src/app/component.ts#L15

Почему так происходит

Как мы знаем, в Redux нужно configureStore с начальным состоянием. Такое начальное состояние представляет собой большое дерево со всеми возможными ключами, инициализированными их редьюсерами. К моменту инициализации никаких действий не выполнялось, ни у одного из редукторов не было никакой полезной нагрузки для разрешения, поэтому в большинстве случаев по умолчанию используется null.

До сих пор стандартный Redux/Angular, насколько мне известно.

Теперь две мои подписки (myUndefined$ и myObjects$) наблюдают за экземплярами AnonymousSubject, которые являются каналом, предоставляемым angular-redux/store для выдачи новых значений, обновленных на ключевом пути (т. е. @select(['myObjects', 'city']) означает <AppState>.myObjects.city).

Проблема в том, что при инициализации начального состояния появляются новые значения для всех известных ключей состояния, но почти всем им назначается null.

Решение или взлом?

Когда есть подписки, прослушивающие любой из этих ключей, такое null будет выдано в качестве первого значения, из-за чего мне нужны нулевые проверки, такие как .filter(x => x !== null) или подобные, во многих местах. Это действительно некрасиво.

Альтернативой является использование Epics (из redux-observables), но они должны быть хорошо спроектированы, а результат пахнет смесью хрупкости с не очень надежным стеком.

У меня сложилось впечатление, что это проблема не только самого angular-redux/store, но и побочный эффект наличия глобального состояния.

Простое решение может состоять в том, чтобы исправить angular-redux/store, чтобы он начинал выдавать значения только после того, как ключ больше не undefined, поэтому избегайте инициализации ключей с помощью null и просто предпочитайте не инициализировать их вообще. Но опять же, если это проблема Redux, я не уверен, что это правильный путь.

Итак, кто-нибудь испытывает то же самое или имеет элегантное решение для этого?


person Marinho Brandão    schedule 23.06.2017    source источник


Ответы (1)


На мой взгляд, это присуще Redux. Концептуально Redux — это архитектура, которая побуждает вас думать о своем пользовательском интерфейсе как о чистом вычислении всего состояния в любой момент времени. Все ваше приложение — это один большой конечный автомат. Поэтому, если вы установите начальное значение null, первое значение этого выбора будет null. Если вы вообще не устанавливаете начальное значение для поля, то первым значением соответствующего выбора будет undefined.

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

Мы могли бы заставить наши Observables «ждать до первого значения, отличного от нуля», до срабатывания, но это именно та временная зависимость, которую я лично использую Redux, чтобы избежать. Я чувствую, что внесение этого изменения в библиотеку Angular-Redux/store, вероятно, не является правильным решением, учитывая, что другие люди, вероятно, этого не ожидают. Я также не уверен, как это повлияет на путешествия во времени и другие инструменты отладки.

Итак, в вашем случае, какие есть более чистые способы справиться с такими ситуациями?

Как правило, я бы рассмотрел один из следующих вариантов:

1) убедитесь, что ваши редукторы всегда имеют начальное состояние.

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

const INITIAL_VALUE = 'some-reasonable-value';
const myUndefinedReducer = (state = 'some-reasonable-value', action) => {
    // etc.
};

Таким образом, вы кодируете значение по умолчанию в самом редукторе.

2) Обработать его при выборе

Это работает для выборок, которые не связаны напрямую со свойством хранилища (например, они являются результатом некоторых вычислений RxJS). Это также работает для случаев, когда начальное значение ключа является динамическим, например. значение, равное undefined, пока вызов API не заполнит его или подобное.

Как вы заметили, это по существу сводится к созданию фильтров в нескольких местах, что может быть многословным. Однако в вашем примере вы могли бы реализовать его более аккуратно, используя select$ и a Transformer:

Некоторый файл Utils:

export const skipNil = (obs$: Observable<any>): Observable<any> =>
    obs$.filter(v => v !== null && v !== undefined);

Компонент.ts:

@select$('myUndefined', skipNil) myUndefined$: Observable<any>;
@select$(['myObjects', 'country'], skipNil) country$: Observable<any>;
person Seth Davenport    schedule 23.06.2017
comment
Спасибо, выглядит разумно. Между прочим, идеальная установка звучит как сочетание 1 и 2, так как в идеале не должно быть ни одного или как минимум null значений в начальном состоянии. Моя точка зрения заключалась в том, что первый undefined никогда не должен выдаваться, так как это означает, что ключ выдается до того, как его обновит какой-либо редуктор, но да, действительно, это было бы сложно для существующих приложений и даже как-то против подхода Redux. Хотя я не знал о select$. Буду использовать его чаще, когда я не могу дать ненулевое значение в начальном состоянии. - person Marinho Brandão; 25.06.2017