Pull to refresh

Comments 85

Вы в статье в основном разбирали только одну проблему редакса — сложности синхронизации с внутренним стейтом реакта. Могли бы описать как ваша PubSub эту проблему решает? Потому что, по-моему эта проблема согласованности данных, и к реакту/редаксу она отношения не имеет.
Например что изменится, если в вашем абзаце
Объясню это так: данные из interal state компонента в подавляющем большинстве случаев попадают в дочерние через props, но, как бы очевидно это не звучало —данные redux при интеграции с react тоже попадают в компоненты через props.

я поменяю «redux» на «PubSub»?

Кстати, чем вам не подошли и имеющиеся PubSub библиотеки, тот же MobX?

После двух лет работы с Redux мне в числе его проблем наиболее важными показались проблемы с продвинутыми руководствами для крупных проектов и недостаточное освящение таких проблем как нормализация данных, разделение стейта страницы и стейта отдельных компонентов, обязательная работа с отдельными элементами через идентификаторы, отделение часто меняющихся данных в отдельные ветки.
В итоге многие хранят в стейте список из 100 элементов тупо массивом и удивляются, когда при изменении одного перерендериваются все 100. А если там у элемента еще и поля для ввода…

Я писал Вам такой развернутый ответ, но при попытке вставить debug скриншот все улетело.
Попробую повторить, но возможно, что-то упущу.


сложности синхронизации с внутренним стейтом реакта

Я писал не об этом, а о том, что на самом деле 1 постулат не соблюдается, что внутреннее состояние конфлитует и конкурирует с redux, что вместе их использовать нельзя. C redux не нужно использовать другие хранилища. Тот же timetravel не предполагает использование внутреннего состояния и ломается, потому что внутреннее состояние зачастую изолировано от входящих пропс.


Могли бы описать как ваша PubSub эту проблему решает?

Изолирует redux от react internal state, служит транспортом и обеспечивает подписку на конкретные данные, вместо подписки на целое состояние. каналы могут быть использованы как отдельно, так и объедены в один, чтобы доставить непосредственно в стейт компонента данные. Да, сабскрайбер пишет в state.


и к реакту/редаксу она отношения не имеет

Redux и React internal state конфликтуют, ломают функциональность друг друга. Допустим, мы еще можем сказать, что Абрамов написал библиотеку универсальной без прицела под React, интеграцию писали другие люди, но facebook приняли Дэна на работу, а redux стал стандартом в реализации flux архитектуры, но вот о том, что они конфликтуют и нужно это учитывать, пишу я, а не Дэн. Если же я просто не читал или не видел такого, а такие заявления есть — поделитесь ссылкой, буду признателен и посыплю голову пеплом.


я поменяю «redux» на «PubSub»?

Вы конечно можете, но это перестанет быть правдой. Я написал выше почему.


Кстати, чем вам не подошли и имеющиеся PubSub библиотеки, тот же MobX?

Отсылка к библиотеке isThirteen вас нисколько не удивила? Объясню: зоопарк решений, зачастую просто неоправданных. В одном проекте и двух-то стейт менеджеров много, а вы спрашиваете почему я не взял третий?
Свой pubsub я использовал, так как он предсказуемый, маленький и мой. я подогнал его под конкретную задачу и смогу использовать в других местах, проектах и т.д. я не говорю, что мой pubsub лучше mobx, разве что он специальный, а mobx или любой другой публичный pubsub пишутся с оглядкой на более широкое и универсальное использование.


В итоге многие хранят в стейте список из 100 элементов тупо массивом и удивляются, когда при изменении одного перерендериваются все 100. А если там у элемента еще и поля для ввода…

Это грустно читать. Руководств такого толка как вы хотите вряд ли будет, хотя есть real-world-app с кучей вариаций, но это примеры, а не руководство. Мой личный рекорд ужаса в проекте с redux — это reducer со switch на 1600+ строк. С мутациями, циклами… в общем, рефакторинг был просто незабываемый.


У редакса много "болезней" и я уже больше года придумываю разные решения для их исправления. Например, избавиться от switch, сделать reducer декларативными, не использовать react-redux для интеграции и многое другое. Я не зря написал, что это первая записка, я буду развивать эту тему и хочу добиться того, чтобы выгоды использования redux не терялись за его недостатками.

Redux и React internal state конфликтуют, ломают функциональность друг друга.

А можно воспроизводимый пример? Как именно они ломают функциональность друг друга? За два года ни разу не видел. Я много видел, когда одни и те же данные хранят и в сторе и в стейте и они идут в рассинхрон. Но именно чтобы две независимых свойства конфликтовали — не видел ни разу. Стор это же просто несколько разных пропсов они могут откуда угодно прийти. Хоть от redux, хоть от mobX, хоть от сторибука.

Тот же timetravel не предполагает использование внутреннего состояния и ломается, потому что внутреннее состояние зачастую изолировано от входящих пропс.

Честно говоря, не сталкивался, может повезло, у меня timetravel просто не трогал собственный стейт компонента. И вполне логично что стейт реакта нужно менять в расширении реакта, а стор редакса — в расширении редакса.
Если ли же у вас внутренний стейт может влиять на стор (напрямую или через пересоздание дочернего компонента и влияния на его методы жизненного цикла) — выносите его в редакс, Flux специально придуман, чтобы не разруливать всю эту синхронизацию.

Это грустно читать. Руководств такого толка как вы хотите вряд ли будет, хотя есть real-world-app с кучей вариаций, но это примеры, а не руководство.

Ну вообще у Абрамова в официальной доке всё это есть. Про не использование массивов чего-то сложнее id-шников даже видео есть. Просто все как всегда читают в документации первые 2-3 главы, а потом удивляется от чего их приложение тормозит.

Мой личный рекорд ужаса в проекте с redux — это reducer со switch на 1600+ строк. С мутациями, циклами… в общем, рефакторинг был просто незабываемый.

Ну ветки swtich либо независимы (ловится линтером через стандартные правила вроде no-case-declarations, no-fallthrough) и тогда там хоть миллион строк может быть, либо даже не знаю данные экшена или стора там что ли мутировали? Тогда как это PR прошло? Выглядит, что Вам просто какой-то говнолегаси от джунов после трехмесячных курсов спихнули.

Воспроизводимый пример есть у меня в проекте, и если у меня будет время — я его приведу в измененном виде. но механизм я привел в статье:


  1. есть редакс, есть изолированный стейт в компоненте.
  2. результаты пользовательского ввода пишутся в стейт, но некоторые действия вызывают диспатч экшенов, обращение к серверу и т.д.
  3. при попытке использовать таймтревел — интерфейс ведет себя не предсказуемо, показывая солянку состояний.
  4. при попытке изменить стейт компонента — тоже самое, стейт меняется, но в интерфейсе — чехарда

Это и есть "ломать".


Курс Абрамова он настолько базовый, что до реального приложения — там как до луны. Я честно не видел массивов в виде стейта в приложениях. Огромной вложенности объекты — да. Но видел, как джуны путают массивы и объекты, это да.


Знаете, для меня switch как goto… Применять его не умеют нормально. Нет, он конечно иногда удобен, но я сторонник декларативного подхода. Если ты по одному полю выбираешь, что делать — используй объект. Да, я знаю, что объект кастует ключи в строки — тут это самое то. Но функция куда лучше ветки свича. Никакой линтер не справится лучше.


Насчет джунов: нет, люди там пишут на js годами. Но подход а ля процедурное программирование или jquery-style в реакт они привнесли. Потому что так привыкли.

Я не зря написал, что это первая записка, я буду развивать эту тему
Вы уверены, что это нужно людям? Сейчас чуть ли не каждый разработчик пытается переделать Redux под себя. Вот статьи только за последние несколько месяцев:
Организация reducer'а через стандартный класс
Redux-symbiote — пишем действия и редьюсеры почти без боли
Redux — пересмотр логики reducer'a и actions
Оверинжинирг 80 уровня или редьсюеры: путь от switch-case до классов

Быть может есть смысл рассмотреть другие библиотеки для управления состоянием, например MobX, где тоже есть логгирование, девтулзы, но не нужно городить над библиотекой свои обёртки, меняя привычные подходы до неузнаваемости, как это сделано в статьях выше.

Я ни в чем не уверен.
Еще одно предложение про mobx) Смотрите почему я не хочу лезть в mobX — опыта нет. Могу или перехвалить, или охаять. Все-таки его нужно использовать, чтобы давать оценку, выявлять несуразности и минусы, которых стоит избегать. С redux я работаю три года — все три года на него не падает спрос, проектов было достаточно как с нуля, так и в очень запущенном состоянии. Я делюсь опытом. В реинжиниринге каких-то вещей, переосмыслении — не вижу ничего плохого. так работает прогресс. Возможно, накопленная критическая масса подтолкнет кого-то создать лучший фреймворк.

В итоге многие хранят в стейте список из 100 элементов тупо массивом и удивляются, когда при изменении одного перерендериваются все 100. А если там у элемента еще и поля для ввода…

Конечно если редьюсер написан таким образом, что он просто возвращает новый список, то перерендрится весь список. Но проблема с перерендриванием частично решается простым return state.map(...). В .map вставляем конструкцию if/else, если элемент из state равен элементу из action.payload, то возвращаем элемент из state, иначе возвращаем из action.payload.
Конечно проблемы в таком случае возникают, например если приходится сравнивать глубокие объекты (ну придется заюзать deepEqual, вроде решаемо), более серьезная проблема связана с тем, что length нового списка и списка из state может отличаться, в таком случае возможно легче вернуть новый список и пускай все перерендривается (но думаю можно повыпендриваться и что-то придумать).
Поэтому кажется, что способы хранить как массив и как объект имеют место жизни, однако я бы сам предпочел хранить конечно объектом.

Так проблема в подходе или в людях? Я вот начал практиковать flutter, и могу сказать что redux я там с большой радостью использую, не представляю другой подход к разработке

Однозначно сказать нельзя. Разработчик может быть великолепным специалистом и при этом не быть архитектором, а ПО — быть более универсальным, чем нужно проекту. В данном случае соединяя несколько библиотек в стек, разработчики не всегда оценивают плохие и хорошие стороны в полной мере. На волне хайпа зачастую могут пропустить некое несоответствие и даже через некоторое время не уяснить, почему мы делаем все по канону, а выходит не то, что ожидалось. В данном случае совместное использование internal state и redux — бомба с часовым механизмом.

Все это конечно хорошо, но я бы небыл столько категоричен. Обновить стейт редакса стоит куда дороже чем локальный (который не совсем локальный но все-же).

Поэтому я и привел аналогию про дешевую еду. В краткосрочной перспективе state выглядит проще. Однако, поскольку я не раз работал с долгосрочными проектами, затраты на поддержку и доработку изначально более "дешевого" подхода выливаются в десятки часов на простые в сущности вещи. Речь о проектах с более чем годом активной разработки.

А вы не думали, что проблема в самом реакте и его архитектуре?

Несомненно в реакте, редаксе и прочих библиотеках куча аврхитектурных проблем. Из-за этих проблем иногда проще писать на чистом js. Но основная проблема в том, что нет библиотеки которая проблем лишена совсем. Чуть сложнее решение — все, есть проблемы. Другое дело знать, как писать так, чтобы проблем этих не получать. Чтобы "не стрелять в ногу"

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

Вот только не попробуешь, не узнаешь, а попробовал — поддерживай. У вас есть серебрянная пуля? Поделитесь, я застрелюсь. А если серьезно, то есть бизнес, требования заказчиков, хайп и много чего, что влияет на выбор и поддержку. Посоветовал бы я редакс кому-то сейчас? Нет. Но и то, это "нет" со звездочкой. Почему? Потому что есть и под этот инструмент свои задачи. Вообще, нет ничего лучше, чем знать свой инструмент настолько, чтобы критика и реальность совпадали.

Вот, смеётесь, вместо того, чтобы поменять топор на бензопилу.

Что посоветуете в качестве бензопилы?

Ну да, почему я сразу не догадался :)

Redux это явный антипаттерн, он создает слишком большую связанность. В среднем SPA несколько десятков страниц с разными данными и разной логикой работы с ними. И пытаясь что-то изменить в одном месте, приходится думать, как это повлияет на все остальное. А понять это крайне сложно, потому что каждую логическую операцию зачем-то разбивают на несколько частей и распихивают по разным файлам.
Никак не могу понять, почему этот бред стал таким популярным
Как объект с данными находящимся в определенном месте может создать бардак? Экшены отдельно, редьюсеры отдельно, саги отдельно. Если ты меняешь экшен то везде поменяется, я предоставить не могу с редаксом то что ты описал…
Так в этом и проблема — «если ты меняешь экшен то везде поменяется»! Я делая один кусочек приложения, должен думать как это повлияет на все приложение. Которое кроме меня еще человек двадцать пишут и я даже не знаю, что там у них должно быть
Ну не знаю, по моей логике например кнопка купить должна везде работать одинакова на всех страницах.

Ну это не антипаттерн, как мне кажется. Паттернов в нем несколько, соединенных в 1. Стор — это пабсаб с 1 каналом. ты всегда подписываешься на все данные в канале. Редакс просто не дифференцирует на что конкретно ты подписан. Он не следит за тем как ты меняешь стейт. Ему вообще все равно что происходит снаружи внутри. ссылка на корневую сущность поменялась — получите и распишитесь. 1 точка входа, одна выхода. это просто с точки зрения внесения изменений, последовательности, предсказуемости. Ну и все. это "state manager" а не "subscribe manager" или "changе manager". Я не выгораживаю тут редакс, а страдаю от этого. Мне не хватает каналов. Когда используешь каналы наглядно видишь разницу — редакс дергает подписчика раз в 40 чаще, обеспечивая 39 ложных срабатываний из 40. Помножить на количество коннектов и становится не по себе. Конечно они работают асинхронно и до поры до времени вы не заметите проблемы, но тем не менее...

Мне кажется вы неправильно понимаете проблемы redux
Представьте сложное spa-приложение, какую-нибудь навороченную админку где юзер может одновременно открыть и расположить по экрану кучу панелей, окошек, cлайдеров, табов, всплывающих попапов с детализацией и т.д. которые будут отображать информацию об одних и тех же данных только с разными фильтрами, сортировкой, аггрегацией, детализацией и т.п. И теперь допустим в отдельной форме меняется какой-то один объект-сущность и нужно обновить все места которые отображают данные этого объекта. И появляется вопрос как определить какие компоненты отображают в текущий момент информацию об объекте который изменился чтобы выполнить перерендер (diff) только этих компонентов а не всего приложения?


С редаксом можно этого достичь только если законнектить каждый компонент и делать полностью плоский стейт где будет соответствие {[айдишник]: объект} а
все one-to-many и many-to-many связи моделировать через айдишники. Это нужно делать потому что независимо от того во скольких местах интерфейса отображается сущность — если данные о ней будут храниться только в одном месте то это избавляет от проблемы ручной синхронизации и необходимости применять изменение во многих местах. А ручная синхронизация еще хуже — вот статья на тему сложности синхронизации — https://hackernoon.com/data-denormalization-is-broken-7b697352f405 — там правда про базу данных на бекэнде но это соответствует модели редакса когда на одно событие может реагировать много редюсеров).
И в результате с редаксом даже с нормализованным стором получается ужасно неудобное решение потому что придется вручную джойнить данные во вьюхах (точнее в mapStateToProps) и обработчиках и любая бизнес-логика где необходимо обращаться к связанным сущностям будет сплошь и рядом пронизана вытаскиванием нужных объектов по их айдишникам.


А вот mobx позволяет не делать нормализацию а хранить объекты в их древовидно-графовом виде. То есть если например есть много таблиц связанных one-to-many или many-to-many то не нужно никаких айдишников — объекты можно прямо связывать ссылками друг на друга. Например если есть приложение где пользователь может создавать папки а внутри папок проекты а внутри проектов задачи и внутри них комментарии то эту схему можно один в один отобразить на объектах — будет объект пользователя в котором будет находиться массив папок ({firstName: "...", email: "", folders: [{..}, {...}, {...}]}), и каждая папка будет хранить объект проекта который будет хранить вложенный массив проектов ( {name: "folder1", projects: [{..}, {..}, {}]}, где каждый объект проекта будет хранить также массив ссылок на вложенные задачи а задача хранить вложенный массив комментариев и т.д, причем еще удобно делать обратные ссылки на родительские объекты чтобы можно было удобно обращаться через точку (например project.folder.priority) а не передавать через пропсы или контекст что требует изменений родительских компонентов.
И таким образом можно удобно работать с данными моделируя связи через ссылки (включая циклические связи между объектами когда вложенный объект имеет ссылку на родителя или в случае many-many связей когда например у проекта может быть много пользователей) и все это позволяет обращаться к нужным сущностям через точку без необходимости джойнить их по айдишникам как в redux.
Вот сравните как "удобно" обратиться к родительской папки из комментария (сквозь промежуточные объекты задачи и проекта) в редаксе когда мы делаем это через айдишники


state.folders[state.projects[state.tasks[comment.taskId].projectId].folderId].priority

А теперь как это проще и наглядней можно делать c mobx


comment.task.project.folder.priority

И mobx при этом еще и обеспечивает точечное обновление компонентов без каких-либо неудобств (достаточно только обернуть поля объектов декоратором observable а сами компоненты декоратором observer) и с большей эффективностью — в отличие от редакс ему не нужно делать цикл по всем подключенным компонентам (чтобы в mapStateToProps сравнить новый и старый объект и понять нужно ли выполнять обновление компонента) — mobx при изменении объекта сразу знает какие компоненты нужно обновить так как хранит список этих компонентов на нужном поле объекта. И если произойдет изменение одного объекта то будет выполнено обновление только тех реактовских компонентов которые в текущий момент отображают этот объект. А если будет происходить изменение сразу многих полей или если компонент зависит то от одних данных то от других данных (всякие условия в шаблоне) то mobx умеет это оптимизировать и выполнять обновление в одном проходе и только тех компонентов которые зависят от нужных изменений в конкретный текущий момент (то есть компонент не будет лишний раз обновляться если поменялись данные в неактивном бранче условия)
В итоге юзеру не нужно думать в каких местах интерфейса одновременно находятся одни и те же данные при их изменении (сколько панелей/окошке открыто) и уж тем более не синхронизировать состояние отдельных компонентов вручную

Наверное самый сложный ответ.


Мне кажется

Да, именно. Вам кажется. Ваш комментарий бьет по вполне понятной проблеме, что редакс плох в плане синхронизации данных, нормализации и прочего. Он отвратителен, несомненно. Тут я написал
https://habr.com/ru/post/445966/#comment_19963356 что оно такое. Проблема которую я осветил в статье лежит в другом месте, если так можно сказать. Я сказал о том, что есть конфликт редакса и реакта, который разрушает архитектуру приложения. При этом ни проблем непосредственно редакса, ни стейта компонента я не касался. Вывод из статьи просто говорит что использование с редаксом других стейт-менеджеров приведет к конфликту и проблемам.


Представьте сложное spa-приложение

Зачем представлять? Я SPA приложения пишу каждый день уже более 4х лет, все они сложные высоко-нагруженные и навороченные. Соцсети, админки, приложения с математикой, графикой и многим-многим другим.


И в результате с редаксом даже с нормализованным стором получается ужасно неудобное решение потому что придется вручную джойнить данные во вьюхах (точнее в mapStateToProps) и обработчиках и любая бизнес-логика где необходимо обращаться к связанным сущностям будет сплошь и рядом пронизана вытаскиванием нужных объектов по их айдишникам.

Странно ожидать от инструмента вещей, которых он не умеет. То есть вы изначально на компонент системы который служит некой оберткой для вашего кода, возлагаете надежды как на постгрес. Постгрес просто не даст вам определять многих вещей, заставит вас использовать типы, отдельный язык и наложит кучу ограничений. Да, взамен он даст много, но и потребует тоже. Еще и отдельный сервер в придачу. Тут вы сами описываете механизм обновления, используя тот же язык что и всегда, ни типов, ни обязательств. И хотите, чтоб оно работало, так же хорошо как бд? Мне кажется тут нужен другой инструмент, а не редакс. Графкьюэль его зовут...


А вот mobx позволяет не делать нормализацию

так и редакс не запрещает. поймите, вы от носка хотите, чтобы он был ракетой.


Вот сравните как "удобно" обратиться к родительской папки из комментария

Шах и мат, Дэн Абрамов. Извините меня за шутки, просто я нахожу это забавным. Вы можете сделать тоже самое и в редакс, просто там ход другой: простые объекты, иммутабельность… Но ссылки можно сделать и там, а еще есть Map, он позволяет связывать объекты. Да безусловно, не имея никакого нужного механизма в чулке под названием redux — можно говорить что это вина redux. Но поймите, он не варит кофе, не стирает трусы, но как чулок — он восхитителен. В любом случае, недостатки редакс я в статье не рассматривал. Понял свой промах, рассмотрю в другой раз. Статья же о clash of redux and react, который мало кто замечает, но который тем не менее влияет на итоговый продукт и на его разработку.

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

так и редакс не запрещает. поймите, вы от носка хотите, чтобы он был ракетой.

Это не редакс не умеет, это он как раз таки и запрещает обычным js-объектам ссылаться друг на друга когда не используются никакие стейт-менеджеры. Почему запрещает? Потому что иммутабельный подход редакса говорит о том что нужно вернуть новый объект состояния. А если изменения произошли где-то глубоко в этом объекте то нужно также вернуть новый объект всех его предков. А теперь что будет если объекты будут ссылаться друг на друга? В простом примере когда юзер может создавать проекты и в проектах задачи и если каждая задача будет вместо айдишника (task.projectId) хранить ссылку на сам объект проекта то уже недостаточно просто вернуть новые родительские объекты — как только происходит создание нового объекта проекта то все его задачи станут ссылаться на старый объект а это значит что нужно будет пройтись по задачам и обновить у них ссылку на новый объект проекта. Но просто обновить ссылку вы тоже не можете — mapStateToProps не увидит изменения и не перерисует компонент, поэтому нужно пересоздать новый объект каждой задачи проекта. Тоже самое с юзером и проектами. В итоге получится что как только объекты будут ссылаться на родительские то обновление одного объекта приведет к рекурсивному пересозданию всех объектов всего состояния. И если в простых приложениях ссылаться на родителя требуется нечасто и можно обойтись айдишником то в сложных приложениях где одни и же же данные могут отображаться в разных местах, а также в случае many-to-many связей проблема становится все актуальней и проще хранить все состояние в нормализированном виде и всегда ссылаться по айдишникам.


По большому счету все стейт-менеджеры решают одну и ту же проблему — проблему эффективного обновления компонентов. Иначе можно было бы просто хранить состояние в виде глобального js-объекта а в нем хранить любую вложенность объектов с ссылками друг на друга,


const state = {
 users: [{
   firstName: "...",
   folders: [
      {
        name: "..",
        user: <link>
        projets: [
           {
             name: "..",
             folder: <link>
             tasks: [
               { project: <link>, name: "", completed: true},
               ...
             ]
           }
          ...
        ]
      }
      ...
   ]
 }]
}

И ссылки на отдельные объекты этого состояния можно удобно передавать компонентам (без всяких джойнов как с нормализованным состоянием в редаксе) и также удобно обращаться по обратным ссылкам ссылкам (обратите внимание на строчку <div>in folder: {task.project.folder.name}</div>)


const App = () => (
  <div>
    <div>name: {state.user.firstName}</div>
    <div>
      {state.user.folders.map(folder=><Folder folder={folder}/>}
    </div>
  </div>
)

const Folder = ({folder}) => (
  <div>
    <div>folder name: {folder.name}</div>
    <div>
      {folder.projects.map(project=><Project project={project}/>}
    </div>
  </div>
)

const Project = ({project}) => (
  <div>
    <div>project name: {project.name}</div>
    <div>
      {project.tasks.map(task=><Task task={task}/>}
    </div>
  </div>
)

const Task = ({task}) => (
  <div>
    <div>task name: {task.name}</div>
    <input type="checkbox" checked={task.completed}/>
    <div>in folder: {task.project.folder.name}</div>
  </div>
)

ReactDOM.render(<App/>, rootEl)

а для изменения объекта достаточно просто изменить нужное поле и вызвать функцию rerender() которая вызовет перерендер реакта (diff) всего приложения


import {rerender} from "./main"

const toggleTask = (task) => {
 this.props.task.completed = !this.props.task.completed;
 rerender();
}

const Task = ({task}) => (
 <div>
   ...
   <input .. checked={task.completed} onChange={()=>toggleTask(task)}/>
   ...
 </div>
)

//main.js

const rerender = ()=>{
 ReactDOM.render(<App/>, rootEl)
}

Таким образом можно удобно работать с состоянием и стейт-менеджер был бы не нужен но вот беда — реакт не умеет быстро выполнять diff всего приложения и на больших spa-приложениях это будет тормозить. И тут на сцену приходят стейт-менеджеры которые как раз и решают проблему тормозов реакта — они соотносят какие компоненты от каких объектов или даже полей зависят чтобы выполнить обновления только нужных компонентов когда объекты изменятся.


И получается что вполне логично оценивать стейт-менеджеры по тому каких удобств они лишают в сравнении с разработкой без стейт-менеджера используя обычными js-объекты. Редакс решает проблему точечных обновлений за счет иммутабельного подхода что лишает всех удобств работы с ссылками (вдобавок к неудобствам писать деструктуризации и собирать новые объекты в редюсерах). А например mobx не требует иммутабельности и позволяет объектам ссылаться друг на друга а проблему точечных обновлений решает по-другому (и версия с mobx за исключением добавления декораторов к полям на классах объектов ничем не будет отличаться от примера выше с обычными js-объектами)

Ну вот насчет айдишников против ссылок, как минимум не уверен. Бывает, что с ними удобнее.
По большому счету все стейт-менеджеры решают одну и ту же проблему — проблему эффективного обновления компонентов. Иначе можно было бы просто хранить состояние в виде глобального js-объекта а в нем хранить любую вложенность объектов с ссылками друг на друга

А что делать, если нужно что-то обновить зная только его id, возможно составной? Например от websoket'а прилетело сообщение вида «обновили поле fieldName, в записи rowId, таблицы recordType».
Нужно проверять все места в сторе где эта запись может быть? Например
  • строка в табличной форме
  • строка при расчетах в графиках дашборда
  • форма на просмотр одной записи
  • строка в связанной таблице формы на просмотр одной записи
  • модалка с формой на редактирование одной записи
  • строка в связанной таблице модалки с формой на редактирование одной записи
  • пользователь скрыл эту форму и обновлять ничего не нужно

Да это из реального приложения пример, и да мне оно тоже кажется слегка перегруженным.

Ладно попроще пример, есть два списка на одном экране в них одна из строк пересекается, как понять, что при изменении в одном списке нужно обновлять эту строку в другом списке?
А что делать, если нужно что-то обновить зная только его id, возможно составной? Например от websoket'а прилетело сообщение вида «обновили поле fieldName, в записи rowId, таблицы recordType».

Я не упомянул этот момент — когда нужно загрузить данные или получать оповещения от сервера то действительно нужен еще хеш где нужный объект можно вытащить зная его айдишник. Но это не значит что мы должны везде оперировать айдишниками — со всеми объектами внутри приложения по-прежнему можно обращаться через ссылки просто для нужд синхронизации данных с сервером на объект также будет вести еще одна ссылка из хеш-мапы по его айдишнику (и удобней будет вынести добавление ссылки на объект по его айдишнику в конструктор базового класса)
И тогда получив оповещение по веб-сокетам просто вытаскиваем по айдишнику нужный объект и обновляем нужное поле. А поскольку все объекты в состоянии связаны по ссылкам и распределяются по компонентам от рутового компонент до самых вложенных через передачу ссылок на нужные объекты (как я привел в примере выше) то неважно во скольких местах одновременно будет отображаться данные этого объекта (форма, таблица, модалка и т.д) — при вызове ReactDOM.render(<App/>, rootEl) или используя mobx (который выполнит обновление только нужных компонентов) произойдет актуализация интерфейса в соотвествии с изменениями

Нужно проверять все места в сторе где эта запись может быть? Например
строка в табличной форме
строка при расчетах в графиках дашборда
форма на просмотр одной записи
строка в связанной таблице формы на просмотр одной записи
модалка с формой на редактирование одной записи
строка в связанной таблице модалки с формой на редактирование одной записи
пользователь скрыл эту форму и обновлять ничего не нужно

Ладно попроще пример, есть два списка на одном экране в них одна из строк пересекается, как понять, что при изменении в одном списке нужно обновлять эту строку в другом списке?

Определением какие компоненты нужно обновить при изменении одного/нескольких объектов/полей как раз и является главной задачей стейт-менеджеров и есть несколько вариантов их решения. Сам реакт также решает эту проблему — вызвав ReactDOM.render() на рутовом компоненте реакт проверит все биндиги всего приложения и применит нужные изменения к дом-элементам (и поскольку один и тот же объект находится через ссылки одновременно в двух разных списках то после его изменения ничего дополнительно делать не нужно — реакт сам увидит что изменились биндинги у одного и второго компонента списка).
Но поскольку на больших приложениях такое решение не очень эффективно (а также еще тот факт что реакт использует не самую быструю технику диффа и сам по себе довольно медленный) и будет тормозить на большом количестве компонентов то поэтому и появился весь этот хайп иммутабельности в реакте — мол давайте будем при изменении возвращать новый объект вместе с его родительскими чтобы можно было переопределить shouldComponentUpdate и тем самым реакт сможет заскипать лишние поддеревья интерфейса при диффе. Но такой подход перестает работать на сложных интерфейсах когда связанные сущности будут передаваться через айдишники (а это необходимо потому что данные могут находиться в разных местах интерфейса и хранить копии объекта во всех этих местах и синхронизировать изменения еще сложнее)
Потом появился redux который выполняет проход по всем подключенным компонентам и это позволяет в mapStateToProps вернуть объект по его айдишнику а редакс сравнит старую и новую версию через === и в случае несоответствия обновит сам компонент. А поскольку в обоих списках будет находиться один и тот же айдишник то когда произошло обновление состояния redux увидит что только два компонента вернули новый объект и обновит только два компонента.

А вот mobx работает по другому — каждое поле объекта которое используется в шаблоне оборачивается в некий обзервабл — объект который помимо значения хранит еще список подписчиков на этот объект. И когда начинается процесс рендера враппер над компонентом устанавливает в глобальную переменную текущий инстанс компонента чтобы каждое поле-обзервабл к которому происходит обращение внутри шаблона смогло добавить к себе в подписчики текущий компонент из этой глобальной переменной чтобы при изменении своего значения вызвать обновление всех компонентов-подписчиков на это поле. Ну а благодаря js-геттерам и сеттерам обращение к таким полям-обзерваблам и изменения их значения синтаксически ничем не отличается от работы с обычными js-объектами, а отсутствие необходимости в иммутабельности и возможность связывать объекты по ссылкам сводит к минимуму количество неудобств связанных с внедрением стейт-менеджера в проект в сравнении с версией когда используются обычные js-объекты и дифф реакта
Я не упомянул этот момент — когда нужно загрузить данные или получать оповещения от сервера то действительно нужен еще хеш где нужный объект можно вытащить зная его айдишник.

Да, в целом разумно.
Определением какие компоненты нужно обновить при изменении одного/нескольких объектов/полей как раз и является главной задачей стейт-менеджеров и есть несколько вариантов их решения.

Там был вопрос как понять что, пришедшие с сервера новые данные частично пересекаются с имеющимися и для них нужно использовать уже имеющиеся ссылки.
Так что тут скорее так же нужно для всех новых данных бегать по хешу. Конструктор объектов придется заменять фабрикой (потому что при создании объекта нужно проверять нужен ли новый или уже имеющийся объект), но в целом должно работать.
А ну еще при использовании id-шников сталкиваемся с проблемой удаление старых данных, прям как в Redux. По идее нужен счетчик обратных ссылок.

Я не буду целиком разбирать ваш комментарий, просто скажу: можете ли вы циклические ссылки сериализовать? думаю, нет. JSON.stringify у вас просто свалится с ошибкой. Передать при серверном рендеринге с сервера? тоже нет? Понятно.


Видите ли, реакт как и редакс — это ужасные инструменты для изоморфных приложений. В них все может быть сериализовано, а после десериализовано — и будет работать также как и до этой операции. Девтулс редакса работают по тому же принципу.


Видимо разработчики посчитали, что наличие серверного рендеринга лучше, чем каша из циклических ссылок. Кстати, циклические ссылки — это антипаттерн. Вебпак из-за них падает, а GC не может сделать свою работу, и это приводит к утечкам памяти. И я не верю в то, что у вас все идеально и ничего не течет, уж простите.


Кстати, mobX работает на сервере? Никогда не встречал еще.

можете ли вы циклические ссылки сериализовать?

Да.


JSON.stringify у вас просто свалится с ошибкой.

Не удивительно, ведь JSON не умеет в графы.


Передать при серверном рендеринге с сервера?

Да запросто. Я даже из субд получаю сразу с циклическими ссылками.


наличие серверного рендеринга лучше, чем каша из циклических ссылок.

Я не улавливаю, чем циклические ссылки могут помешать серверному рендерингу.


Вебпак из-за них падает

Есть сборщики, которые не падают.


GC не может сделать свою работу

Вообще-то GC именно для циклических ссылок и придуман. Без них достаточно было бы лишь умных указателей.


я не верю в то, что у вас все идеально и ничего не течет, уж простите.

А вы редаксовый стор как чистите?


mobX работает на сервере?

А что же ему может помешать работать на сервере?

image


Пожалуйста, напишите какие инструменты и бд, сборщики вы используете. Очень интересно. Звучит как кулстори, если честно.


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


Гарбадж коллектор придумывался для автоматической чистки памяти в целом, и циклические ссылки — это лишь 1 из задач с которыми он должен уметь работать, в отдельных версиях ie GC не мог в циклические ссылки, так что заявление — что он для них и придумывался — сомнительно.


Как я чищу стор? Легким движением. :)


А что же ему может помешать работать на сервере?

Не видел примеров использования на сервере за три года. Может скудная документация.

в отдельных версиях ie GC не мог в циклические ссылки

Уточнение: он не мог в циклические ссылки если где-то в цикле был COM-объект, т.е. внешняя неподконтрольная GC сущность. Во всех остальных случаях даже ie с циклическими ссылками справлялся.

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

https://orientdb.com/
https://github.com/eigenmethod/mam


Как я чищу стор? Легким движением. :)

То есть не чистите?


Не видел примеров использования на сервере за три года.

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

То есть не чистите?

Чищу. Извините, это был ответ, симметричный вашим:


Да.

Да запросто.

Есть сборщики

Очистка стора делается просто. Вместо того, чтобы в объединенный редьюсер передавать предыдущее состояние, при определенных экшенах подаем undefined. в итоге получаем исходное состояние.


Ну вот mam сборщик использует похожую штуку на сервере

именно mobX? или просто обсерверы? Мне нравится, что вы продвигаете инструменты eigenmethod, но это все равно не mobX на сервере в продакшене.

Вместо того, чтобы в объединенный редьюсер передавать предыдущее состояние, при определенных экшенах подаем undefined. в итоге получаем исходное состояние.

Насколько я понял это очистит вообще весь стор, а не только удалит не нужное.


именно mobX? или просто обсерверы?

А MobX чем-то принципиально отличается?

Насколько я понял это очистит вообще весь стор, а не только удалит не нужное.

По вашему вопросу не понятно, совсем чистить или не совсем.


А MobX чем-то принципиально отличается?

Другая библиотека? MobX стейт менеджер, а mam — нет? MobX является частью экосистемы, а mam или просто обсерверы — нет? Достаточно принципиально?

А зачем в приложении чистить стор совсем?

mam — сборщик, использующий библиотеку $mol_atom, аналог MobX.

при логауте, например.


mam — сборщик, использующий библиотеку $mol_atom, аналог MobX.

супер! но тем не менее, это не mobX… Вопрос то был про то, зачем редакс если есть mobX. Ответ был про заточенность на серверный рендеринг, и про отсутствие примеров серверного рендеринга с mobX. И опять же, пусть мы берем mam в расчет: 12 звезд против 18.9k у mobX, 47.8k у redux. $mol — 200 звезд… Библиотека может быть сколько угодно хорошей, но пока нет сообщества, экосистемы — нет и специалистов, не спроса у продуктовых и аутсорс компаний… Вам лучше пиарить эти инструменты на митапах, вебинарах и конференциях, написать больше статей, примеров и прочего. Собрать сообщество и экосистему…


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

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

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

зачем? может еще и старый комп сжигать, чтоб наверняка?


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

Ага, а потом с помощью таймтревела злоумышленник восстанавливает весь стейт.

Злоумышленник при наличие доступа к учетке просто перезайдет с сохраненным в браузере паролем. Или килоггер поставит

Кейлоггер без пароля админа не поставить. Ну и сохранять пароль на машине с которой собираются выйти, никто не будет.
Кейлоггер без пароля админа не поставить.

Пишут, что работает. Не знаю правда работает ли в этом режиме дневник ввода, но думаю, что да. Раньше на него даже антивирусы не ругались.

Итого для такого вектора атаки необходимо:
1) Чтобы был доступ к машине после пользователя, но не было до этого.
2) Но чтобы было время прогонять js-скрипты в консоли.
3) И чтобы пользователь боялся сохранять пароли в браузере.
4) Но не боялся заходить под чужой учёткой.
5) И не закрывал после этого вкладку.
И атаковать нужно пользователей можно строго по одному.

Так себе вектор для атаки.

P.S. iframe для формы логина работает не хуже, но при этом защищают логин/пароль/данные кредитки от такого вектора атаки как XSS. Который вполне себе реальный способ атаки.

Вы как-то однобоко смотрите. С чего вы взяли, что атаковать нужно больше одного пользователя? Что приватные данные — это только логин и пароль?


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

По кнопке «выход» нужно принудительно выгружать страницу именно для того, чтобы в памяти гарантированно ничего не осталось.

Вы таким образом не почистите: cookies, localStorage, indexedDB, историю браузера, кеш браузера.
Почему Вы думаете, что забыть убрать девтулзы с продовой сборки можно, а забыть убрать ветку пользовательских данных из серриализатора стора в localStorage — нельзя? Я вот на второй кейс не так давно наталкивался.
Единственное что ваш подход с перезагрузкой страницы дает — ложное чувство безопасности. Типы мы заботимся о безопасности и закрыли одну дырку, аудит безопасности можно не делать. Пофиг, что аудит безопасности найдет ещё 99 дырок.

Что приватные данные — это только логин и пароль?

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

Ну кто в здравом уме оставляет в продакшене девтулс? таймтревел доступен только оттуда. не выдумывайте...

Уж точно не те, что пишет код без багов.
А пока кто-то ставит звездочки пакетам, которые проверяют «а не 13 ли ты часом»

ахаха это пять)))
Вы работаете на новом для вас проекте, или ваш коллега писал некую функциональность год назад, а вам теперь ее нужно расширять

Год назад коллега написал нужную фичу в (условно) 2-3 строчки кода (fetch(url).then(() => this.setState())).
Через год требования поменялись и надо провести рефакторинг — перенести данные в глобальный стейт.
По-моему, ситуация стандартная и беспроблемная. Более того, сейчас, зная новые требования, вы формируете данные в глобальном стейте сразу в нужной форме.
А могло статься, что этой новой фичи бы не было — и старый код работал бы без проблем еще год.


Имхо, проблема только одна (как и указано в статье) — ломается тайм тревел.
И тут уже надо смотреть по ситуации, насколько полезен тайм тревел для простого случая (загрузили данные -> показали на экране) по сравнению с оверхедом, который он привнесет.

Год назад коллега написал нужную фичу в (условно) 2-3 строчки кода

Я не работаю на таких мелких проектах… Было бы счастьем считать, что в реальных приложениях только такие фичи и больше никто ничего не трогает и не добавляет.


только одна

в статье написано больше, однако…

только такие фичи

А я и не говорю про "только такие". Но такие тоже есть. Более того, даже в сложных фичах зачастую есть элементы, где какие-то данные загружаются и используются единожды (что вписывается в схему fetch.then(setState)).
Вы в статье призываете всегда использовать редакс вместо состояния компонента — даже для подобных случаев.
Я же утверждаю, что есть сценарии, где состояние компонента более уместно.


в статье написано больше

В статье есть секция "проблемы" из двух пунктов. Первый — react dev tools — по-моему, какой-то очень граничный случай. Я, например, никогда с этим не сталкивался. Остается только проблема с тайм тревел.

Секция действительно из 2х пунктов, однако проблем больше:


У нас появляются всякие техники типа lift state up (это когда данные нужны сиблингу хост элемента, поэтому часть state и всю логику работы с ней выносят в родителя, тратя уйму времени на переписывание и тестирование рабочей логики) и т. д. Появляются баги, оверхед в доработках и много-много радости.

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

основная проблема наличия internal state — он конкурирует с redux за данные, проигрывает в долгосрочной перспективе

Постараюсь лучше структурировать статьи впредь.


Вы в статье призываете всегда использовать редакс вместо состояния компонента

Это где? Вот что я пишу:


Не используйте вместе с redux других хранилищ.

И уже потом, подразумевая, что вы все-таки используете redux с react написал:


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

А еще я пишу, что отказался от redux в пользу pubsub и внутреннего состояния для конкретной цeли. Недостаточно иметь инструмент, важно знать когда и как его правильно использовать.


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

lift state up

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


начинаете путешествие по дереву компонентов

Я обычно с этого и начинаю. Смотрю, откуда компоненту пришли эти данные (предполагаем, что с кодовой базой я не настолько хорошо знаком). Так все равно проще, чем искать данные в развесистом дереве редакс (развесистое — ибо используется по всему приложению).


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


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

Вот это преимущество локальности конфликтует с тайм тревел, который основан на едином глобальном состоянии. И тут уже нам надо выбрать — что нам важнее в данном конкретном сценарии.

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


И да, зачем вам редакс, если вам проще использовать внутреннее состояние, не понятно. Я не настаиваю на том, чтобы вы отказывались от внутреннего состояния. Я говорю о том, что они с редакс конфликтуют, и чтобы избежать конфликта и нестабильности нужно не использовать 1 из вариантов. Но поскольку я начал с того, что "нельзя просто так взять и использовать редакс", то и закончил с позиции редакса. Этого требует целостность повествования. Отказаться от редакса — тоже решение данной проблемы.

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


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


Локальный стейт мне для одноразовых данных. И еще для состояния интерфейса, которое живет только на фронтенде (например, флаг "открыт ли выпадающий список").

Простите, если понял вас неправильно.


Еще раз повторюсь: предмет статьи в том, что 1й постулат редакса не работает с react internal state. Отсюда вытекают дополнительные сложности.


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

Приведу конкретный пример — для более предметного обсуждения.


Задача
На странице данные, связанные с различными твиттер аккаунтами (можно добавить какой-то аккаунт в список, указать к нему настройки, специфичные для приложения, batch-операции со списком).


И есть доп возможность — новые аккаунты в список добавлять не вручную (ввод с автодополнением), а добавить полностью список, привязанный к твиттер-профилю текущего пользователя (https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-list.html).


Выглядит примерно так:


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

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


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


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


Да, тайм тревел не отобразит данные в этом модальном окне немедленно (вместо этого, тайм тревел приведет к маунту компонента, что, в свою очередь, вызовет запрос к апи твиттера).
Поскольку логика тут примитивная (апи запрос -> показали на экране), необходимость отладки именно этого куска через тайм тревел тут маловероятна.


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


Какие конкретные минусы в данном подходе видите вы?

Я, если честно, вижу тут следующее:


Пользователю с медленным 3g придется ждать вечность, пока подгрузится список. он закроет окошко не дождавшись...


Как поведет себя ваше приложение? Упадет при резолве ответа, ведь компонента больше нет? При повторном открытии выкинет данные и пошлет опять запрос и пользователь опять не дождется?


У компонента всегда есть родитель, который может сказать вашему компоненту "до свидания". Тем самым весь прогресс теряется и начнется проделывать все заново. Чтобы сделать актуальность — достаточно запрашивать не чаще чем 5-10 секунд с прошлого ответа.


При этом стор как правило живет, пока живет приложение. Соответственно: если запрос был секунду назад или еще в процессе вы и так получите актуальные данные. И не важно сколько раз тапнет пользователь. При этом сам компонент не будет отвечать ни за что кроме рендеринга — это основное назначение компонента, а не запросы к серверу.


Есть понятие зона ответственности, ее не нужно нарушать, тогда приложение будет поддерживаемым, простым, тестируемым и более надежным.


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

Разделение ответственности и использование глобального / разделяемого состояния — ортогональные концепции.
Нет никаких проблем отделить презентационную часть (разметку) от логики — при этом логика будет работать с локальным стейтом контейнера, а не с глобальным.
Все, что вы написали выше, без проблем реализуется на локальном уровне.
В чем преимущество использования именно глобального состояния для этой задачи?


Заметьте, я не пишу конкретно про редакс. Замените его на Mobx, любой другой Flux или что-то самодельное — ничего не поменяется. Противопоставление именно глобального состояния и локального.


Я оспариваю именно ваш призыв использовать везде глобальное состояние и избегать локального.

Я написал какие проблемы. Хотите еще? Окей, допустим вы вынесли локальное состояние и получение данных выше, чтоб наверняка, в контейнер страницы. У вас появилась необходимость прокидывать пропсы через иерархию. Это приводит к связанности компонентов и ререндеру для прокидывания нового состояния. То есть компоненты, которые организуют передачу будут вызывать рендер, чтобы ваш список получил данные, а не чтобы перерисоваться. Все начинает притормаживать — реакт в фоне делает пересчеты. Как итог, вы получаете свои данные, а пользователь — тормозящий интерфейс.


Расскажу вам кулстори 2х летней давности. Попал я на проект одной иностранной соцсети. И был там чатик. Все бы хорошо, но чат был просто отвратительно написан. я тут в комментариях писал про редьюсер в 1600+ строк — это как раз редьюсер чата. рефакторинг занял у меня 4 дня, я удалил более 11000 строк суммарно из кода чата и связанных модулей.


В чем с ним была проблема? Ну кроме спагетти кода, написанного крайне небрежно, мутаций и прочего.
Если открыть чатик в хроме — он работал, притормаживал, но вполне жил, скрепя байты, делал свою работу. Турбофан все же делает свое дело...


Если открыть в фф и попытаться написать — символы появляются с задержкой до 6 секунд. А если пишут тебе — то интерфейс становился еще мертвее.


Проблема была в посылке статуса тайпинг, хотя это и не единственная проблема. Просто кто-то жмет клавишу — а у тебя окно чата 627 раз зовет рендер (я написал простенький счетчик вызовов, чтоб разобраться). И вот если у тебя не мощный комп, а тогда я ради эксперимента пересел на ноут из днс с пентиумом, ты превращаешься в хатико. Ты резко ненавидишь разрабов, которые сидят на своих компах с топовым железом и не думают про планшеты и дешманские ноуты. А ведь если думать о пользователях соцсетей — они выходят в сеть с чего угодно, но не с компа с 6-ядерным intel восьмого поколения и 32 гигами оперативки.


Так вот, оказалось, что этот самый "user typing..." (а точнее флаг) пропс дриллингом прокидывался через иерархию компонентов к месту вывода. Ну и получается суммарно 627 рендеров на нажатие клавиши у твоего оппонента. Стоило вынести тайпинг в стор и конектнуть именно строку которой нужен флаг, и лаг снизился раз в 5. а количество ререндеров стало 1.


Поэтому я все понимаю, подход ваш тоже, но использование редакс, как и неиспользование, может привести к страшным последствиям, если не думать.

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

То есть основной архитектурный принцип Реакта является полнейшей глупостью. Но вы продолжаете колоться, плакать и продолжать есть кактус писать костыли.

Что вы злой такой? При чем тут костыли? Любая библиотека имеет недостатки и требует затрат для изучения, правильного использования и оптимизации, а все разработчики допускают ошибки в планировании и разработке — они люди, разработчики библиотек тоже, как ни странно. Непогрешимых нет. Другое дело делают ли они работу над ошибками и учатся новому, или нет. Вы тут хейтите по чем зря. Вашу позицию выяснили — мы глупые, жрем кактус, однако по щелчку только в комиксах мир меняется. Такое ощущение, что вы существуете в отрыве от реальности. Не надо так.

Попробуйте для разнообразия вместо того, чтобы выискивать личные оскорбления, подумать о чём собеседник говорит. Вы сами признаёте фундаментальную проблему дизайна Реакта, из которой вытекают почти все проблемы при работе с ним. Единственная работа над ошибками, которая тут возможна — выпиливание Реакта полностью и использование чего-то иного. Да хоть тот же Vue или Svelte из популярного имеют куда более продуманный дизайн, чем этот уродец из Мордора Фейсбука.

Я не выискиваю. Вы просто твердите одно и то же, и поверьте я вас услышал. Я написал вам, почему реакт и редакс.
Повторяю: есть реальные приложения, которые приносят прибыль. Они живут годы, появились тогда, когда лучшими были реакт и редакс. Они работают так, как хотят того пользователи, а аналогов нет. Работают хорошо! Переписывать что-то с инструмента на инструмент, потому что прошлый инструмент "устарел" или в нем есть недостатки, которые при должном мышлении и подходе нивелируются — чушь. это как айфон покупать каждый год, просто потому что вышел новый, а не потому что старый сломан. Коммерческое ПО так не разрабатывают. У нас есть продукты на вью — они новые, потому что вью появился недавно. А по поводу уродца из Фейсбука — да, вы правы. Этот инструмент — продукт жизнедеятельности конкретной компании, хотя впрочем как и большинство профессиональных инструментов. Он покрывает нужды той компании, откуда произошел, а не нужды всех и каждого. Но для большинства приложений его хватает. Тут дело в разработчиках скорее всего уже — на какой бы классный инструмент вы не посадили человека, ему все не то и не так. Это инфантильный подход: выбрасывать работающие вещи, потому что они не модные или их выпустил не Иван Ю. Любой продукт со временем переписывают, учитывая прошлые недостатки, но не потому что так сказал человек в интернете. Ощущение, что я должен объяснять всем и каждому одни и те же прописные истины.


Ну вот смотрите: я делаю как сказал винтаж. бросаю все и бегу от реакта с редаксом, как от чумы. беру самый новый фреймворк из возможных, основываясь на бенчах в интернете. И начинаю пилить. Происходит следующее — меня спрашивают почему вместо нового функционала, который хочет видеть потенциальный крупный покупатель в нашем ПО, я переписываю страничку авторизации? Почему я заново делаю эти панели и окна, которые заморозили как стабильные еще в позапрошлом году? А я говорю: реакт отстой, а вот убер-вафля, которую написал спутник19103(ник вымышлен), — будущее интернета и срочно надо все переписать. На меня смотрят как на идиота и просят вернуться к разработке проекта и не страдать фигней. Это в лучшем случае.


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


Вы правы, это стоит того. А последствия — да пёс с ними.

Они живут годы, появились тогда, когда лучшими были реакт и редакс.

Не было такого никогда.


Происходит следующее — меня спрашивают почему вместо нового функционала, который хочет видеть потенциальный крупный покупатель в нашем ПО, я переписываю страничку авторизации?

Нет, вас спрашивают: почему новую функциональность вы реализуете так долго, используя кривую технологию, требующую специальных приседаний для оптимизации и согласования поведения? А вы отвечаете, что 2-3 года назад было принято неправильное решение и теперь мы до конца дней своих будем заложниками выбранной архитектуры, так что добавьте ещё людей в команду, чтобы скорость разработки не снижалась.

Ваши утверждения мало согласуются с действительностью.

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

Эм… это был пример про связанность) да, на редаксе можно многое наворотить, не спорю

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

С таким советом нужно быть очень аккуратным. Посмотрите доклад Козули на Moscow Frontend Conference 2017 youtu.be/ZijpBIO452w?t=755
Они там тоже все в сторе хранили, и кончилось все очень плохо, пришлось все обратно из стора в локальный стейт выносить.

Как я писал уже выше в ответе на один из комментариев — я начал повествование про редакс, соответственно выводы были с его стороны. Откажитесь от редакс — это тоже решение. Опять же смысл статьи не в том, чтобы призвать всех хранить в редакс, а в том, чтобы показать несостоятельность постулата и конфликт.

Особых проблем с разделением state & store у меня не было. Кроме как в самом начале, пока разбирался.


А вот когда к уже готовому приложению попробовал приделать router, тут я поплыл. Судя по всему подход redux не совместим с самим принципом наличия ещё 1 полноценного источника правды (URL-address). Любые изменения в маршрутизации могут потребовать тонну рефакторинга и костылей. А если в качестве роутера ещё какой-нибудь ReactRouter...


Для себя сделал вывод — если у приложения предполагается рутинг — уделить ему предельно много внимания на самых ранних стадиях. Иначе будет много боли.

Вот смотрю я сколько у людей проблем с роутингом что на ангуляре, что на реакте, и недоумеваю, зачем всё так через жопу делать. Это ж тривиальная штука показывать разные вещи в зависимости от текущего урла.

В React я думаю проблем нет. Речь именно про Redux. Сама философия располагает к тому, чтобы всё важное было в store. А store штука непростая. Он должен быть устроен мудро. Рефакторинг структуры стора — боль. И когда в приложении появляется ещё 1 полноправный источник истины возникает вопрос как это всё синхронизировать. Да ещё и так, чтобы не нужно было продумывать рутинг в мелочах ещё до создания остальной архитектуры приложения. И вот тут прямо всё плохо. Скажем захотеось добавить какое-то поле в URI-параметры, чего раньше в них не было, привет большие проблемы. Очень много всего переделывать, включая, вероятно, и тесты.


В общем я пока не понял как правильно готовить сложные SPA с нормальной маршрутизацией в условиях часто меняющегося ТЗ. С каким-нибудь observable там легко. Обычно observable = бардак и нет никакой сложной фиксированной иерархии. Стало быть и нет проблем этой иерархии.

Согласен с вами, организация роутинга в приложении с редакс вызывает проблемы. Конечно стало несколько проще с появлением 4 версии роутера, но проблем это не решило.

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

Пожалуй, самый плохой совет, который только можно дать начинающему разработчику. Еще не было ни одного кейса за мою практику работы с React и Redux, где разделение store & component state вызывало бы какие-либо проблемы. Возможно, проблема существует лишь в вашей голове, а отсутствие code review в вашей команде вносит в это свою лепту.

Настоятельно не рекомендую следовать совету автора. Почитайте лучше, что пишет сам автор Redux по этому поводу.

Так статья не для начинающих(о чем я в начале написал). Это не призыв, а проблемы я описал. Но видимо каждый замечает то, что хочет и не смотрит на остальное… Это хорошо, что вы приводите ссылку на комментарий в issue, но хотелось бы пояснений в документации или более популярных источниках.


Проблема, которую я описал, а именно конфликт состояний и поломку таймтревела описана в документации redux тремя строками. Локальный стейт используют не думая и не разбирая. Редакс используют так же не разборчиво. в цитате, которую вы приводите, есть уточнение, которое как по мне снимает претензии

UFO just landed and posted this here

Спасибо, постараюсь лучше

Sign up to leave a comment.

Articles