Обновить

Комментарии 16

Красиво.
Качественная работа!
Отдельное спасибо за код.
Как ограничить время жизни State при данном подходе? В большей части случаев хранить состояние одной фичи все время жизни приложения нет смысла. Или задача отдельной библиотеки?
Зависит от того, что именно Вы хотите получить. Вряд-ли Вам нужно ограничивать время жизни всего State, скорее какой-нибудь его части, так ведь? Приведу пример, если я правильно понял вопрос. В ApplicationState можно положить BottomSheetState, который будет отвечать за отображение вот такого UI элемента. У него могут быть различные вариации для отображения различных состояний, но, если у Вас в настоящий момент времени скрыт BottomSheet — просто ставите state = null и всё, на стороне UI соответственно подписка на эту часть состояния и отображение / скрытие UI элемента в зависимости от значения.
image

Интересный подход сам по себе.
Главный вопрос — на сколько сильно раздувается AppState в большом приложении?

Настолько сильно что потом становится тяжело свести концы с концами, и понять какая цепочка экшенов отработала. Кроме того служебные расходы на перегенерацию стейта сильно превысят полезную нагрузку. Кроме того redux один не идет, к нему еще понадобится цепочка библиотек которые костыляют проблему синхронных экшенов и лишних перерисовок, всякие саги, реселекты и прочее. Вообще redux позиционирует себя как масштабируемое решение, но есть одно НО, относительно чего? А хорошо масштабируется оно относительно jquery скриптов, а относительно чистой архитектуры он наоборот создает проблему масштабирования.

Я приводил пример подхода чуть ниже в ответ на другой комментарий. Вкратце — зависит от того, как Вы построите состояние. Моё мнение в чем: При другом подходе данных меньше у Вас не станет, они просто будут разбросаны по всему приложению, часть во фрагменте, часть в интеракторе, часть ещё где-то и т.д.
В случае с Redux они просто лежат в одном месте, а всё остальное эти данные только использует, но не хранит.

В AbstractStore передаются списки middleware и reducer'ов, и судя по коду, обработка происходит в том же порядке, в котором они и находятся в этих списках. Не создает ли это проблем, когда нужно добавить изменения в существующую фичу?

Да, последовательность действительно имеет значение в данном случае и я бы даже сказал, что надо быть внимательным, но, это редко создаёт проблему, если правильно к этому подойти. Как правило у Вас будет мало случаев, когда несколько разных Middleware следят за одним и тем же событием и пытаются его заменить на что-то другое. Шанс выстрелить себе в ногу, конечно, всегда есть, но больших проблем я особо не заметил, да и логика эта хорошо покрывается тестами, что даёт большую уверенность в отсутствии регрессий. Отличный вопрос, кстати.

Спасибо! Но как быть, в случае если отправляем событие A и это событие в результате хождения по миддлварам подменяется на событие B, но в редюсере нужно менять стейт при событиях A и B? Создавать новое событие AB, которое будет подразумевать события A и B не очень хочется
Сложно дать однозначный ответ, потому что эта логика очень зависит от конкретной ситуации. Т.е. если Вам на событие А нужно что-то загрузить из сети и потом отправить событие В, то в middleware можно просто запустить запрос поймав событие А и при этом вернуть событие А дальше, таким образом, оно дойдет до reducer'a. А когда запрос выполнится — сделать dispatch события B. Либо же можно делать dispatch из самого middleware, но это нужно делать осторожно, потому что редукс синхронный сам по себе. Вообще, очень хороший вопрос и это своего рода краеугольный камень, с событиями надо быть осторожным, особенно когда речь идёт о замене одного на другое.
Уже давно пользуюсь похожим решением, но со стейтом на каждом фрагменте / активности, а не одним стейтом на все приложение.
Возникает вопрос. А не происходит мощного усложнения читабельности единого стейта в крупных приложениях?
В принципе можно выносить в подстейты целые флоу, которые в свою очередь тоже дробить на стейты отдельных экранов, но как-то больно много вложенности, читать же неудобно… Какое решение Вы используете для таких случаев?

Ну и второй вопрос — пробовали дробить на отдельные стейты? Проблемы переживания поворота и прочего вполне легко решаются, если сделать store на базе обычной ViewModel от гугла, а каждая фича становится полностью независимой, в любой момент по feature модулям можно приложение бить.
Если пробовали, какие минусы и плюсы по ощущениям от сравнения Вашего варианта и отдельных стейтов?

В более сложной конструкции части ApplicationState описываются иначе.
Например как-нибудь вот так:


data class ApplicationState {
    ... some other data here
    val bottomSheetState: BottomSheetState,
    val currentScreenState: CurrentScreenState
}

При этом BottomSheetState и CurrentScreenState могут являться sealed классами с явно ограниченным количеством возможных вариантов


BottomSheetState.kt


sealed class BottomSheetState {

    data class SimpleMessageBottomSheetState(...) : BottomSheetState()

    data class MessageWithButtonBottomSheetState(...) : BottomSheetState()
}

CurrentScreenState.kt


sealed class CurrentScreenState {
    data class Home(...) : CurrentScreenState()

    data class Profile(...) : CurrentScreenState()

    data class Settings(...) : CurrentScreenState()
}

Т.е. получается что у нас есть стейт какого-то BottomSheet'a или конкретного экрана и в момент времени активным может быть только один из. Но в тоже время в ApplicationState рядом могут лежать и другие данные, которые каждый может дергать на любом экране. Мне кажется, что в целом это баланс, между работоспособностью и достаточно простым на восприятие состоянием. Создатели Redux на JS в одной из частей документации даже описывают подход, как лучше структурировать состояние. Вкратце — более плоское состояние — лучше, сильно много вложенностей усложняют работу с такой конструкцтей.
Если под дроблением вы имели ввиду что-то другое и я не до конца ответил на Ваш вопрос — дайте пожалуйста знать, подискутируем.

А как это все дружится с разбиением приложения на модули?

Зависит от ситуации. По принципу вынести что-то в общий commons модуль, а остальное в другие — нормально. Если речь про feature модули, тут, к сожалению, подсказать не могу, ввиду отсутствия опыта в реализации подобного подхода.

Интересно. Как по мне — не хватает некой сущности event для того чтобы вывести простые события из store наружу, например показ диалога с ошибкой / snackbar / ну или чего-то подобного. Выносить подобные вещи (single event) в state как-то трудоемко.
Еще позволю сделать наблюдение что не все action должны быть видны снаружи store, например при некоем абстрактном search должен быть action = SearchChanged который посылает view в store, он обрабатывается middleware, который загружает данные и собирает некий внутренний action = DataUpdated, который в свою очередь обрабатывается reducer и изменяет state. Так вот по моему мнению action=DataUpdated не должен торчать наружу, т.к. нарушается инкапсуляция и у разработчика появляется дополнительная возможность чего-нибудь сделать не так.

Для показа Snackbar'a в примере со вторым приложением я тоже использовал middleware. Он следил за единственным интересующим его событием и показывал сообщения, когда что-то прилетало. Я примерно понимаю что Вас смущает в случае с событием DataUpdated, то, что оно пролетит через все Middleware и другие Reducer'ы, потенциально давая возможность прострелить колено, но, в этом весь Redux, это фундаментальный его механизм, сложно будет сделать иначе не нарушив изначальную идею.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Информация

Сайт
wheely.com
Дата регистрации
Дата основания
2010
Численность
101–200 человек
Местоположение
Великобритания