Записка frontend-архитектора #1. Нельзя просто так взять и использовать Redux

в 13:10, , рубрики: javascript, react.js, ReactJS, redux, архитектура приложений, записки, ликбез, личный опыт, Проектирование и рефакторинг, рефакторинг

Дисклеймер

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

image

Что не так с использованием Redux?

Тут я надумал написать, а что собственно не так с использованием redux в моем проекте и тысячах других? Я же пишу приложения на react / redux эдак с апреля 2016 года(три года). Пора бы уже открыть некие интересности… А то лекции и доклады, особенно нацеленные на новичков, были, а вот какого-то взрослого взгляда назад и ретроспективы не было. А пока кто-то ставит звездочки пакетам, которые проверяют «а не 13 ли ты часом», я буду разбивать стену сложившихся стереотипов.

Но redux не нужен!

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

Мы используем Redux, потому что это принятый стандарт(во всяком случае для react), нам важна предсказуемость и надежность, которая есть у Redux. Но мы ее серьезно недополучаем, собственно в этом

Претензия

Наверное, чтобы было понятно, в чем претензия по пунктам, нужно сперва вернуться в прошлое, к истокам, как угодно, а именно открыть документацию славного redux и прочитать постулаты. Я рассмотрю только первый из них. Если вам будет интересно — делитесь в комментариях, разберу еще много моментов.

Единственный источник правды…

Вот здесь у нас засада. Конечно, говорится там о том, что сторов может быть 1, redux декларирует свое отличие от flux архитектуры таким образом. Но если смотреть шире: соблюдается ли правило в реальном проекте? Вы скажете: вполне. Я же декларирую(объявляю) 1 стор, его передаю в провайдер и тогда…

Технически процесс описываете верно. Но я могу сказать, что люди подвержены искажениям восприятия, логики, и, поскольку программисты все еще люди… Ну вы поняли, к чему я веду(если не поняли: программисты склонны к искажениям восприятия, логики и т. д.)
Главное искажение тут в том, что я, как и многие мои коллеги, привыкли, что если мы не юзаем термин «store» по отношению ни к чему, кроме redux, то и store других нет.

И тут приходит реакт

Одна техническая особенность, которая зовется internal state, плевать хотела на этот постулат. Она просто создает стор типа internal state в любом удобном месте. Есть компонент — есть стейт, который имеет механизм обновления и влияния на компонент. Отличие в целях использования (хранить стейт, обновлять и транслировать изменения) — почти незаметны. Вы можете возразить: не вполне понятно, чем тебе не нравится стейт компонента. Он же не такой как редакс, как их вообще можно сравнивать! Он внутренний, ну там флаги хранить какие.

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

Масштаб у redux и state react-компонента разный, но суть, при их сегодняшнем использовании, одна.

Объясню это так: данные из interal state компонента в подавляющем большинстве случаев попадают в дочерние через props, но, как бы очевидно это не звучало —данные redux при интеграции с react тоже попадают в компоненты через props. С точки зрения компонента, который принимает props — неважно, что снаружи. То есть для конечного потребителя — что redux store, что internal state — одинаково.

Еще этот internal state может не зависеть от props того компонента, в котором он объявлен. Тогда получается изоляция, которая делает такой internal state еще большим store, чем можно представить.

Чтобы он был внутренним по-настоящему, он должен влиять только на компонент, где объявлен, не давая утечек в дочерние компоненты. Сложно? Весьма, ведь смысл его почти весь теряется. Это еще 1 признак того, что internal state — это store. Ведь мы просто убрали из назначения «хранить стейт, обновлять и транслировать изменения» один пункт — трансляцию изменений. Все, стейт потерян.

То есть основная проблема наличия internal state — он конкурирует с redux за данные, проигрывает в долгосрочной перспективе и гадит. У нас появляются всякие техники типа lift state up (это когда данные нужны сиблингу хост элемента, поэтому часть state и всю логику работы с ней выносят в родителя, тратя уйму времени на переписывание и тестирование рабочей логики) и т. д. Появляются баги, оверхед в доработках и много-много радости. Это все шум, который портит наше ПО еще на этапе разработки. Мы не докатились до прода, но ПО уже такое себе.

То есть по всем признакам и проблемам, у нас есть больше одного стора и много проблем с этим связанных. Финальной, наверно, вещью станет следующее:

Я очень люблю redux еще и за то, какой devtools у него есть. Когда я начинал мы использовали логгер, который просто консолил все экшены, не давая, впрочем, цельную картину происходящего. Теперь же — это основной помощник и друг. У React они тоже представлены. Вообще, devtools — причина по которой любому pubsub далеко до Redux. Как муравью до синего кита.

Проблемы(пруффов не будет — DNA):

  1. изменение internal state через react devtools иногда не приводит к обновлению или желаемому результату — грешу на интеграцию с redux.
  2. internal state ломает timetravel в redux devtools. Супер фича с путешестивием во времени, доступная благодаря архитектуре redux не работает благодаре архитектуре react internal state. Internal state просто плевал на изменение в redux, у него свое состояние. Таймтревел просто не выходит. Часть элементов просто не обновляется, частично обновляется и т. д. Вся эта эпопея с синхронизацией асинхронного кода коту под хвост.

image

Пример, конечно же, высосанный из практики

Вы работаете на новом для вас проекте, или ваш коллега писал некую функциональность год назад, а вам теперь ее нужно расширять. В общем, есть задача на доработку чужого кода. Вы начинаете расследовать, и понимаете, что в редакс данных нет. В коде нет экшенов, редьюсеров, что хранят нужные вам данные. И вы начинаете путешествие по дереву компонентов поисках заветного и находите их (!!!) даже несколько штук. Вы спрашиваете у коллег, но ответ стандартный: я не помню, в стейт писать быстрее, у нас не было времени и прочее. Вы идете в исходники и понимаете, что текущее его состояние не предполагает доработку. Вы переписываете рабочий оттестированный код, чтобы внести изменения и добавить новую функциональность.

Наличие пагубной альтернативы в виде internal state делает свое черное дело. Ведь сейчас это дешево, и не важно, что будет через год.

Немного метафор

Похоже на плохую еду — вроде вкусно и дешево, но через год или 3 — ЖКТ перестает подчиняться и живет своей жизнью. Вы тратите много времени и денег на возвращение былого здоровья и не всегда в этом преуспеваете.

Redux и React Internal State — конкуренты, как крупный и мелкий бизнес в одной нише. Основной товар — данные и влияние. ПО — потребитель их продукции. Можно привести много аналогий, но суть остается одна — когда мы разрабатываем ПО — нам конкуренция не нужна.

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

Кхм, что-то меня понесло. Все должно идти по плану, в общем. У нас спринты, релизы и прочее, а у ПО конечная стоимость и время жизни/выхода на рынок. Это очень важно, и мы не можем позволить бунт на корабле, восстание библиотек.

Вывод простой

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

Пример

Я разрабатывал standalone модуль в одной ветке и рефакторинг store в другой — вообще мой подход к управлению store и state отдельная тема для публикации. Я начал рефакторинг до модуля, но на момент как начала, так и окончания написания модуля, рефакторинг в тест и в мастер не ушел. Рефакторинг большой и требует вдумчивого регресса, который нужно запланировать — в общем, нельзя просто взять и отрефакторить.

image

Поэтому, зная о грядущих изменениях в store, я не стал использовать его для разработки новой фичи. Это увеличило бы затраты на актуализацию заброшенного рефакторинга и тестов в разы.
Что я сделал: подписался на минимум данных. Данные и их структура не страдали от рефакторинга, страдал код который их формировал, сохранял и т. д. В редакс не пишу ни байта. Проверяю залогинен ли пользователь и еще пару полей.

Для своих нужд я запилил PubSub с каналами и простым API. Да, да, пабсаб. Отсутствие нормальных девтулзов боль. Тайм-тревела — боль. В общем, я планирую написать расширение для хром в виде devtools и возможно опубликовать на гитхабе реимплементацию как конкурента redux. У меня есть тонна претензий к redux, которые я в этой статье поднимать не стану, но такой вот PubSub их практически не имеет. Кроме того, что я вспомнил redux logger…

И таким образом у модуля собственное хранилище, свое соединение с сервером.

То есть redux совсем не влияет на модуль, практически не может повлиять на это хранилище(всего три поля в подписке), но и модуль, и PubSub на redux никак не влияет. Такое разделение исключает конкруренцию.

Вопрос «а где хранить данные?» при разработке модуля у меня не возник ни разу. Но когда речь об redux vs internal state — у многих этот вопрос возникает практически постоянно. Я решил ответить на этот вопрос раз и навсегда.

Мое архитекторское мнение такое:

Храните данные только в redux(все-все, даже «внутренние»), если он подключен к вашему react-проекту в качестве основного хранилища, не используйте хранилищ которые будут конкурировать с ним. Это повысит надежность и отдачу от этой библиотеки и ее devtools(таймтревел и слежение за всеми данными по шагам ускоряют разработку и поиск возможных проблем — синхронные изменения дебажить круче и проще асинхронных).

Возможно стоит дописать библиотеку которая полностью исключает internal state из разработки? Или подменяет internal state на выборку из redux, например? Одну такую я начал писать еще год назад, закончил на 90 процентов, даже провел 1 доклад. Что скажете? Нужны такие?

Надеюсь, эта записка вам понравилась :)

Автор: kurt_live

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js