Comments 632
У вас получился MV без C с последующими проблемами: жесткая завязка представления на модель с невозможностью подменять последнюю.
Сontroller может быть связан только с одним View
Сильное заявление, проверять я его конечно же не буду.
А еще это this.views.forEach((view: React.Component) => view.setState({}))
просто лютая жесть. Мало того, что в реакте есть специально для этого forceUpdate, мало того, что с непонятного перепуга модель должна знать о всех вью, на нее подписанных, так еще фактически убивается использование shouldComponentUpdate
… Погодите-ка… Он ведь вызовется и в нем можно проверить модель. Спорим, что это просто случайная "фича", а не обдуманный ход.
Возможно, такое решение является не самым лучшим, но его легко изменить, и оно не влияет на суть статьи.
Оно изменяет суть всей статьи, а именно попросту неграмотное и противоречащее использование всего реакта, получившего свое название от слова "реактивность". Ближайшее аналогичное, но при этом работающее, решение — mobx, который скрывает в себе подписку, однако, как и прочие, рекомендует передавать значения через параметры.
Сейчас вы же попросту построили backbone на React, что создает аж два вопроса: зачем он на реакте и чем сам бэкбон в таком случае не угодил?
Redux слишком сложный, и я говорю не про количество строк кода в репозитории библиотеки, а про те подходы к разработке ПО, которые он проповедует.
Чего в редуксе сложного? Никогда этого не понимал. Есть стейт с состоянием, есть редьюсеры, которые этот стейт меняют, есть экшены — фактически события, которые вызывают эти редьюсеры.
Вот что с редуксом не так — так это работа с асинхронность. Подход что "асинхронности не существует, а если уж очень нужна — ну используйте middleware" звучит странно и породил тысячи различных библиотек, а экшен, который через 5 секунд породит другой экшен все еще вписывается в редукс криво.
Ну и еще немного критики:
onClick={() => this.model.remove()}
Стандартный вопрос на собеседовании: почему так делать не стоит и как это писать правильно.
Где у BaseModel
метод componentWillReceiveProps
?
У вас получился MV без C с последующими проблемами: жесткая завязка представления на модель с невозможностью подменять последнюю.
Создаем новую модель с таким же интерфейсом и заменяем старую, в чем проблема?
Controller есть — это callback'и onClick, onBlur и прочие.
Сontroller может быть связан только с одним View
Сильное заявление, проверять я его конечно же не буду.
Можете проверить, это несложно. Откройте описание MVC в Smalltalk-80 и найдите пункт "The View — Controller Link". В этом пункте написано:
Unlike the model, which may be loosely connected to multiple MVC triads, Each view is
associated with a unique controller and vice versa.
с непонятного перепуга модель должна знать о всех вью, на нее подписанных, так еще фактически убивается использование shouldComponentUpdate
Опять же цитата из описания MVC:
In that case the object which depends upon the model's state — its view — must be notified that the model has changed. Because only the model can track
all changes to its state, the model must have some communication link to the view.
…
When a new view is given its model, it
registers itself as a dependent of that model. When the view is released, it removes itself as a
dependent
Действительно можно использовать forceUpdate, спасибо. На самом деле нужно было у базового View реализовать метод modelUpdated(model: BaseModel), но я поленился.
Стандартный вопрос на собеседовании: почему так делать не стоит и как это писать правильно.
Правильно так:
handleClick = () => this.model.remove();
onClick={this.hadleClick}
Но выглядит это немного громоздко
Где у BaseModel метод componentWillReceiveProps?
А зачем он нужен модели?
А зачем он нужен модели?
У baseView, ошибся. Нужен, если модель поменяли, привязаться к новой.
По поводу хандлера опять неверно, суть не поменяется. В данном случае ничего не мешает забиндить метод у самой модели, а не создавать пустые анонимные функции на каждый рендер.
Про modelUpdated у базовой вью тоже не совсем верно. Так уж сложилось, что в реакте для этого принято использовать High ordered component, по типу редуксовского коннекта, observer из mobx-react и подобного. Почему? Реактивность, все дела, вы не вмешиваетесь в стандартный жизненный цикл компонента и все прочее.
Но это все мелочи и про реакт. С одной стороны сложно воспринимать критику редукса при поверхностном владении реакта, с другой — большинство из этих ошибок не носят идеологический характер и легко правятся.
Если говорить идеологически то а: смалталк не единственный канон MVC, обычно распространено все таки явление что контроллер и вью развязаны, на это есть причины (хотя бы посмотрите где этот смалталк сейчас, а сколько реализаций с более привычным подходом)?
Б: вы все ещё превращаете реакт в кривую версию бэкбона, от чего в целом при его создании и пытались уйти,
В: весь фронт в 99% случаев и есть один большой вью. Его задача — отрисовать некое состояние, затем отправить событие на бэкэнд, дождаться изменений и отрисовать новое состояние. На фронте моделей как таковых, как хранителей и реализаций бизнес-логики практически нет, а что вы называете моделью как раз и является ответной частью контроллера бэкэнда, задача которого является отправить команду в бэкэнд (условно вызвать метод модели) и получить результат. Редукс, а точнее — единый стейт, некое глобальное состояние, поэтому так хорошо и заходит на фронте, покуда фронт по-прежнему не более чем хитрый шаблонизатор. Простой пример — если у вас есть несколько моделей, использующих одно и то же значение (фейсбук приводил в пример количество непрочитанных сообщений), то в стейте оно всегда будет храниться и браться из одного места, когда «модельки» же в свою очередь зачастую будут хранить свои экземпляры. На бэке, к слову, тоже есть такой же стейт — база данных. Но только модели, которые общаются с базой данных, делают это синхронно, поэтому там это заходит. Хотя никто не мешает использовать такой же подход на фронте, но выглядит это немного бессмысленно.
Вообще рекомендую ознакомиться с mobx, они делают что-то похожее, только на стероидах. Правда они тоже рекомендуют использовать единый стейт, и такой же connect.
P.s. Я не защищаю редукс, он мне жутко не нравится, лично я бы смотрел в сторону cyclejs, но там тоже не все гладко.
У baseView, ошибся. Нужен, если модель поменяли, привязаться к новой.
Я понял сам кейс, но не могу представить случай, когда в рантайме нужно будет поменять модель у компонента. Если это реальная необходимость, то функциональность всегда можно дописать, это же Software. За такую гибкость его многие и любят.
Про modelUpdated у базовой вью тоже не совсем верно. Так уж сложилось, что в реакте для этого принято использовать High ordered component
HOC — это же лишь подход, и при использовании MVC я им принебрегаю и обновляю компоненты при изменении модели. Плюс ко всему, как я написал в статье, мне не нравится сама идея Container Components.
хотя бы посмотрите где этот смалталк сейчас, а сколько реализаций с более привычным подходом
Не стоит презирать этот язык только потому что он сейчас не фигурирует на рынке. Благодаря Smalltalk мы с вами можем как пользоваться UI, так и программировать его. Smalltalk дал толчок развитию Apple и персональных компьютеров. Если будет время и желание. предлагаю прочитать The Early History Of Smalltalk.
вы все ещё превращаете реакт в кривую версию бэкбона, от чего в целом при его создании и пытались уйти
Но я же использую React по назначению — только как View. Моя реализация модели никак не отражается на удобстве его использования. А если вы видите, что отражается, то всегда можете изменить реализацию BaseModel и BaseView, я лишь направил вектор размышлений по направлению оригинального MVC.
Простой пример — если у вас есть несколько моделей, использующих одно и то же значение (фейсбук приводил в пример количество непрочитанных сообщений), то в стейте оно всегда будет храниться и браться из одного места, когда «модельки» же в свою очередь зачастую будут хранить свои экземпляры.
Не будет много моделек, Facebook обманывает :)
Будет модель Chat
, которая содержит список Thread
. Каждый Thread
имеет количество непрочитанных сообщений. Chat суммирует количество всех непрочитанных сообщений в Thread'е и возвращает их с помощью getCountOfUnreadMessages()
. При получении нового сообщения Thread
оповещает Chat
, что он обновился.
Вообще рекомендую ознакомиться с mobx, они делают что-то похожее, только на стероидах. Правда они тоже рекомендуют использовать единый стейт, и такой же connect.
На mobx заглядывался, но руками не трогал. Видимо, пришло время потрогать, спасибо.
но не могу представить случай, когда в рантайме нужно будет поменять модель у компонента.
Я думал — тоже не приходит в голову слету. Но в любом случае это хорошая практика — если подписываетесь на какие-то параметры в didMount, то не забывайте их менять в willReceiveProps. Рано или поздно это стрельнет в ногу.
По поводу реакта как вью — тут вопрос сильно глубже. В данном примере можно докопаться до прямого вызова методов, но вот что больше интересно — как вы решите сопутствующие проблемы в виде роутинга? Да и вообще обобщить: кто порождает модели? Кто делает асинхронные запросы? Я так понял, за все отвечают модели, реакт только отображение. Хорошо, не спорю, поддерживаю всеми руками.
По поводу компонентов высшего порядка — а выбор то небольшой. Лезть хуками в жизненный цикл компонента не стоит, потому что внутренности могут меняться, потом будет куча геморроя с миграциями на новые версии как при миграции на 16 версию. Я просто рекомендую, я понимаю что наследование выглядит крайне уместно, но проблема в том что за жизненный цикл отвечает не сам базовый компонент как таковой, а то, что его рендерит (именно поэтому все методы публичные) и могут возникнуть проблемы например с enzyme.
Книжку почитаю, спасибо, хайповость функционального подхода подзамылила глаза, признаю.
Теперь про пример с фейсбуком. Дело в том, что помимо Chat есть так же SideBar, который так же должен отобразить эту цифру, и есть шапка — в итоге три места. Нужно либо шарить между ними Chat, либо искать другой метод. В ангуларе, шарпе, Джаве и всем здоровом мире используют DI для этого, но в реакте вот религия не позволяет. На самом деле, проблемы то и заключаются в том, когда разные элементы страницы начинают между собой общаться событиями, возникает вопрос кто кого должен порождать, кто кого передавать и все такое. Проблемы начинаются именно тогда, когда зависимости асинхронны, например что-то внизу должно среагировать на то, что что-то вверху изменилось, да ещё и подтянуть актуальные данные. Модели начинают знать слишком много друг о друге, им нужно подписываться друг на друга. Это попытались решишь через однонаправленный поток событий и возможность любому компоненту выловить любое событие. Потом кто-то насмотрелся на хаскель, захотел чистых функций и все такое и создал редукс. Иммутабельнотсь стейта скорее техническое ограничение, чем прихоть. От идеального MV* проблема все равно не решится.
Можно было бы решить иначе? Можно. Только оказалось что идея заходит хорошо, причём не важно — редукс, мобх — проблема асинхронного общения компонентов лучше решается однонаправленный потоком событий, например через ещё более древний подход — single bus вроде называет, когда все события закидываются в общую шину.
Mobx — да, прям советую. У него тоже не без проблем, но фактически он просто уберёт с вас будущие проблемы с подпиской/отпиской и всем таким.
Теперь про пример с фейсбуком. Дело в том, что помимо Chat есть так же SideBar, который так же должен отобразить эту цифру, и есть шапка — в итоге три места.
Chat, SideBar, Header — это все View, которые могут содержать в себе другие View. На словах можно рассказывать слишком долго, поэтому приведу пример кода, в котором буду использовать описанные в статье BaseView и BaseModel.
Я бы создал модель чата:
import BaseModel from "./Base/BaseModel";
import ThreadModel from "./ThreadModel";
export default class extends BaseModel {
threads: ThreadModel[] = [];
get unreadMessagesCount() {
return this.threads.reduce((count: number, thread: ThreadModel) => {
return count + thread.unreadMessagesCount
}, 0)
}
threadUpdated() { this.updateViews; }
}
А затем CounterView:
import * as React from "react";
import BaseView from "./Base/BaseView";
import ChatModel from "./ChatModel";
export default class extends BaseView <ChatModel, {}> {
render() { return (
<span>{this.model.unreadMessagesCount}</span>
); }
}
После чего инстанцировал бы все модели и общие компоненты на уровне PageView, например. А в HeaderView передал бы инстанс ChatView в качестве props'а:
import * as React from "react";
import BaseView from "./Base/BaseView";
import HeaderModel from "./HeaderModel";
import CounterView from "./CounterView";
export default class extends BaseView <HeaderModel, {counterView: CounterView}> {
render() { return (
<header>
<section>Facebook</section>
{this.props.counterView}
</header>
); }
}
Если CounterView нужно как-то кастомизировать внутри HeaderView, то можно в HeaderView передавать ChatModel через props'ы и инстанцировать CounterView уже внутри HeaderView. Или можно использовать ChatView как child внутри HeaderView. Возможно, есть более элегантное решение, но я бы сделал так.
Интересная беседа получается, спасибо за нее.
Могу сразу сказать, что при таком подходе вам придётся таскать сверху вниз огромную кучу моделей. Спор как это решить действительно идёт с 80х, причём как в C++, C#, так вон и в фронте, потому что у каждого решения есть свои недостатки (поправьте если я не прав и есть прям золотое универсальное решение).
Но пойдём опять к уровню фейсбука. Сразу оговорюсь что далеко не все делают приложения настолько сложными, и у 99.9 процентов настолько глубоких проблем не будет, но все любят закладываться под будущее.
Итак, нашу шапку, левое меню и сам чатик (в двух экземплярах) разрабатывают разные команды, они даже монтируются как разные реакт приложения. Причём в разное, произвольное время, а часть логики может просто отсутствовать (зачем грузить код самого чата, если он не отображается на экране. Разве что лишний объём памяти и передачи данных в сети гонять), но цифру отобразить нужно. И меняться она может из трёх мест (две реализации чата и сообщение от бэкэнда). И не должна расходиться, вот требовательный у нас ПО. Подскажу: вам ничего больше не останется кроме как создать модельку без собственного представления, на изменения которой будут подписываться другие модельки. А, да, тесты ещё...
Ладно, задачка переусложнена. Но проблема с TodoMVC, что на нем все хороши, а в реальном мире многие подходы оказываются не такими уж и работоспособными и оказывается, что редуксы всякие придумали не просто так. Но Вот из моей личной практики.
Есть запрос /project, по которому рисуется весь каркас приложения, шапка и прочее. Есть запрос /data, по которому рисуются данные в табличке. Пока все нормально. А теперь есть кнопка «подключить источник данных», которая сперва отправляет запрос на /add-source, рисует иконку загрузки в шапке и в таблице, когда он выполнился, начинает опрашивать каждые 10 секунд проект на предмет изменения флага, после чего идёт обновлять данные и только после этого убирает иконку загрузки и показывает реальные данные на своих местах. Нажать кнопку можно в двух местах. Начнём с этого. В действительности все гораздо сложнее, например пользователь за это время может банально переключить на другое представление и впустую ходить за данными смысла нет, ещё проекты могут измениться на сервере независимо, их вообще могут сбросить, самих кнопок три группы по 6, при этом данные берутся из большего количества источников и мне кажется нам пора переделывать API (злой комментарий про то, что на бэкэнде сделают как им удобнее, а потом «че вы так все переусложняете»).
P.S. Я использую rx и счастлив, в целом то. Там такое делается просто, но есть другие проблемы.
Подскажу: вам ничего больше не останется кроме как создать модельку без собственного представления, на изменения которой будут подписываться другие модельки.
Второй раз минимум в треде вижу " модельку, на изменения которой будут подписываться другие модельки". Это не MVC. В MVC одна модель, скрывающая за собой всю логику, кроме логики UI. В случае фронта, модель — это слой за которым спрятан и локальный стейт в памяти, и взаимодействие со стораджами, и с серверами, всё, кроме UI. В примере с чатом где-то в модели будет свойство "количество сообщений", на изменение которого подписываются вьюхи. Неважно почему оно изменяется, вьюхи будут получать сообщение, что оно изменилось.
P.S. Уверен, что вы лично это понимаете, но очень уж часто моделью называют именно класс, хранящий данные по типу ActiveRecord. Когда вижу что-то типа class User extends Model биться головой об стену хочется. :)
Вид вообще и не в курсе о модели (и даже, возможно, о том, что она есть). Он просто предоставляет некоторое апи, по выводу информации пользователя, модель ее дергает. Либо вид может запрашивать у модели данные. У кого он запрашивает данные (и кто пользуется его апи) — не волнует никого.
Он просто предоставляет некоторое апи, по выводу информации пользователя, модель ее дергает
Простите, а можете написать псевдокод, как это выглядит? Ну то есть в какой момент модель решает, что надо отрендерить ту или иную вьюшку? Как она решает, какую именно и какие данные ей будут нужны? Как в коде выглядит знание модели о вьюшке?
Она это знает, это же модель. В ней хранится вся информация о стейте приложения.
> Как в коде выглядит знание модели о вьюшке?
Как доступ к публичному апи. Вид предоставляет некий метод, рендерящий то, что надо, модель его вызывает.
Ну в общем то же самое, что с моделью и контроллером.
class view {
public viewMethod(...) {...}
}
class model {
public modelMethod() { view.viewMethod() }
}
Ну то есть в какой момент модель решает, что надо отрендерить ту или иную вьюшку? Как она решает, какую именно и какие данные ей будут нужны?
Это вопрос ее спецификации.
> Как она решает, какую именно и какие данные ей будут нужны?
public viewMethod(...) {...} — какие аргументы во viewMethod, те данные и нужны. Спецификацию метода мы ведь знаем. Момент показа тоже известен из спецификации. Для определенности — пусть будет
class view {
public showCart(...) {...}
}
Этот метод может дернуть или модель (если надо показать корзину при определенных изменениях модели, каких именно — это спецификация модели), или контроллер (опять же, по спецификации, например, при клике на какую-то кнопку). Сам вид вообще в данном случае пассивен, он ничего не решает, а только предоставляет апи (содержит саму логику отображения корзины, но не отвечает за сам факт ее вывода), активна модель. MVC позволяет и другой подход — когда вид сам опрашивает модель и решает, что делать. И тот и тот подход допустим (но, наверное, их нежелательно смешивать).
Если модель активная — то затем, чтобы обновлять view.
> Модель не может работать без вью совсем?
Может, если пассивная.
Знает. У нее необходимо есть ссылка на вид, как она может не знать?
За тем, что объект А не может взаимодействовать с объектом Б если нет ссылки. Если вы отправляете некое сообщение кому-либо через подписку — у вас есть ссылка. Иначе бы сообщение не дошло.
> Вы ссылки на все эти вью будете пихать в модель?
Все пихают, чем я хуже? Если другого способа не придумано.
> В лучше случае у модели будет ссылка на ивент диспатчер
А ивент диспетчер имеет ссылку на вью. Таким образом, модель, транзитивно, имеет ссылку на вью. О чем я и говорю.
Да какая разница? Важно, что view.someMethod() это ровно то же самое, что dispatcher.notify(«someMethod»). В обоих случаях отправитель прибит гвоздями к апи получателя. Он должен знать это апи и понимать его семантику. И прямая у вас ссылка или транзитивная — тоже совершенно не важно.
Так и в случае наличия ссылки на вид виды эти могут быть разными. Единственное требование — они должны предоставлять некий апи (реализовывать интерфейс). Это требование совершенно одинаковое как в качестве отправки сообщений, так и в случае вызова метода. В обоих случаях модель знает о спецификации данного апи, которое должно быть реализовано получателем.
Именно о них и знает. Вы не можете отсылать сообщение потребителю, если не знаете, какие сообщения он принимает и какова их семантика.
> В случае MVC должна соблюдаться независимость V от M в контекстном плане.
Еще раз, когда говорите о babylon-MVC, то называйте его babylon-MVC.
> Главное, чтобы методы одной части не влияли на методы другой.
А это невозможно. Если в области Х вызывается метод Y, то значит в этой области должна быть известна семантика данного метода.
Отсылаете-то вы не диспетчеру, а тому, кому потом перешлет диспетчер. С точки зрения того, кто отсылает, диспетчера нет, он прозрачен. Вы же когда звоните какому-то человеку по телефону, то не рассматриваете это как звонок АТС? Вы рассматриваете как звонок данному человеку. Которого вы знаете, и с которым способны осознанно общаться. а АТС как бы и не существует, вы не замечаете ее работы. Так и тут — вид и модель не замечают работы диспетчера, будто его и нет.
> Но управление или проброс осуществляется через контроллер.
Нет, не осуществляется. В MVC запрещено передавать данные из модели в контроллер. Это ключевая особенность MVC. Двунаправленный обмен данными может быть между моделью и видом и (в исключительных случаях) между контроллером и видом (обычно взаимодействие между видом и контроллером происходит через модель, она в данном случае выполняет функцию прокладки между ними, взаимодействие между контроллером и видом напрямую допускается, например, при подписке видов на модель). В случае же обмена контроллер->модель он строго однонаправленный.
Только не MVC, а MVP, и не контроллер, а презентер.
2006
aspiringcraftsman.com/2007/08/25/interactive-application-architecture
2007
Нестарообрядцы — это кто? Вчерашние выпускники?
Это уже вообще бредни. Никогда и нигде контроллер не используется как «источник данных». Источник данных в MVC сидит за моделью и самим MVC не описывается.
Кроме того, это противоречит даже вашему описанию выше — если контроллер находится _между_ моделью и видом, то уж точно он не может находиться _за_ моделью.
Или у вас просто смешались вместе кони, люди и прочее и вы путаете MVC на стороне клиента и MVC на стороне сервера (там интерфейс — это реализация работы с протоколом, например, хттп, контроллер — отвечает за обработку полученных запросов, модель — это бд (условно), а вид — слой, отвечающий за сборку хтмля на выдачу).
> Я же понимаю MVC так как я указал по ссылке. Я не помню когда первый раз услышал про MVC. PureMVC это был фреймворк который я юзал.
Ну то есть какой-то дурак сказал глупость, а вы теперь упорно ее повторяете.
Та стрелочка — это не данные с клиента. Там вообще нету ни сервера ни клиента. У вас горячка.
Именно. Стрелочка ведёт от контроллера к модели, стрелочка показывает поток данных, контроллер передаёт модели данные, а не наоборот.
Модель не знает об API view или controller. Она предоставляет им своё API, в частности она определяет механизм подписки и доставки сообщений об изменении своего состояния, которым они будут пользоваться. Она определяет его синтаксис и семантику. Вью при подписки на события модели должно строго соблюдать их, для адекватного отображения нужного ему среза стейта модели. Для модели же вью — абстрактный подписчик на её данные по её API, реализующий её интерфейсы типа /App/UI/MVC/Model/EventSubsriberInterface для подписки на её события /App/UI/MVC/Model/Event. Модель знает только как отправить такое своё событие подписчику, реализующему такой её интерфейс и прошедшему её флоу подписки на события. Вью это, логгер, транслятор событий во внешнюю MQ-систему — ей всё равно. Семантика вызываемых методов этого подписчика для модели ровно одна: "я сообщаю в удобной мне форме удобным мне способом подписчику, прошедшую удобную мне процедуру подписки, о том, что со мной случилось такое-то моё событие, в котором он высказал заинтересованность. Я своё дело сделала, обработает ли подписчик это сообщение с той семантикой, которую я в него вложила, мне всё равно".
Это зависит от конкретной реализации. Если вид получает от модели сообщения, то модель знает АПИ вида, по определению. Ведь она знает, какие сообщения ему слать и когда. А это и есть АПИ.
Подстраивается модель.
Мы уже это обсуждали. В этом случае ваше приложение просто не работает. Вам же нужно получить работающее приложение? Значит, вам придется подстраивать модель.
Например, вашему виду надо вывести некие данные. Модель эти данные не содержит. Вам надо изменить модель (подстроить под вид) так, чтобы она эти данные содержала. Еще вы, конечно, можете прям из вида сделать запрос и эти данные минуя модель получить. Но мы ведь такие варианты не рассматриваем в всерьез?
2VolCh
> Нет, сообщение, механизм подписки и механизм доставки — это API модели.
Механизм подписки и доставки — это апи механизма подписки и доставки. Не модели и не вида. Просто ваша модель и вид не взаимодействуют с механизмом доставки (в том смысле, что не замечают его), они взаимодействуют друг с другом. То, что там есть какой-то промежуточный механизм — они вообще должны быть не в курсе, желательно совсем не в курсе (то что на практике они все-таки об этом в курсе — просто еще один случай протекшей абстракции).
Механизм подписки и доставки — это апи механизма подписки и доставки. Не модели и не вида. Просто ваша модель и вид не взаимодействуют с механизмом доставки (в том смысле, что не замечают его), они взаимодействуют друг с другом. То, что там есть какой-то промежуточный механизм — они вообще должны быть не в курсе, желательно совсем не в курсе (то что на практике они все-таки об этом в курсе — просто еще один случай протекшей абстракции).
В рамках MVC механизм доставки сообщений не выделяется, он или в модели, или в виде. Поскольку видов много разных, а модель одна, то место ему в модели. Этот механизм часть API модели, модель предоставляет любому виду свой API чтобы любой вид, включая те, которых даже в проекте не было на момент создания модели, мог получить её актуальное состояние. Механизм может быть сторонний по факту, но входить он будет в API модели.
Например, вашему виду надо вывести некие данные. Модель эти данные не содержит. Вам надо изменить модель (подстроить под вид) так, чтобы она эти данные содержала.
Хоть один практический пример такой ситуации можете привести? Как могут появиться требования к виду, ссылающееся на что-то, что ранее не появилось в требованиях к модели? напомню, что бизнес-заказчик ничего о наших видах и моделях не знает.
Он не выделяется потому, что является деталью реализации и, еще раз, потому что ни вид ни модель вообще про этот механизм знать-то и не должны. Он должен быть прозрачен (максимально).
> Механизм может быть сторонний по факту, но входить он будет в API модели
У вас куча путаницы. Это апи модели в рассматриваемом вами случае будет входить в апи механизма передачи сообщений. Потому что этот механизм будет изо всех сил притворяться самой моделью. Поменяете апи модели — поменяется синхронно и апи передатчика.
> Хоть один практический пример такой ситуации можете привести?
Да постоянно. Любое серьезное изменение требований к подобным вещам и сводится.
> Как могут появиться требования к виду, ссылающееся на что-то, что ранее не появилось в требованиях к модели?
Потому что любые бизнес-требования формулируются как требования к виду (естественно, тот, кто требования формулирует, может и не знать, что это называется видом, но он формулирует требования именно к той части системы, что реализована как вид, если мы используем MVC). Мы же уже это обсуждали. «ваша система должна предоставить возможность клиенту насовать товары в корзину и оповестить менеджера о ее содержимом» — требования к виду.
Он не выделяется потому, что является деталью реализации и, еще раз, потому что ни вид ни модель вообще про этот механизм знать-то и не должны. Он должен быть прозрачен (максимально).
Они должны о нём знать, иначе как они им будут пользоваться?
У вас куча путаницы. Это апи модели в рассматриваемом вами случае будет входить в апи механизма передачи сообщений. Потому что этот механизм будет изо всех сил притворяться самой моделью. Поменяете апи модели — поменяется синхронно и апи передатчика.
Откуда у вас путаница? Апи модели состоит в рамках MVC из апи для изменения состояния модели, которым пользуются контроллеры, и апи для предоставления состояния модели, которым пользуется виды. Не механизм притворяется моделью, а модель предоставляет механизм, скрывая за ним свою внутреннюю сложность.
Да постоянно. Любое серьезное изменение требований к подобным вещам и сводится.
Любое серьезное изменение требований сводится, вернее разбивается на параллельным изменения требованиям к модели, видам и контроллерам.
Потому что любые бизнес-требования формулируются как требования к виду (естественно, тот, кто требования формулирует, может и не знать, что это называется видом, но он формулирует требования именно к той части системы, что реализована как вид, если мы используем MVC). Мы же уже это обсуждали. «ваша система должна предоставить возможность клиенту насовать товары в корзину и оповестить менеджера о ее содержимом» — требования к виду.
Вот ничего тут о виде вообще. Это о модели в чистом виде. Это уже аналитик или архитектор может заметить неявные требования к виду, что, скорее всего, пользователю ещё и нужно видеть и товары, и корзину, чтобы не вслепую насовывать, и неявные требования к контроллеру, что скорее всего пользователь насовывать будет путём нажатия экранных кнопок, ссылок и т. п., а не вызовы апи модели курлом дергать.
Бизнес-требования к системе разбиваются на:
- что система должна делать, что хранить, что показывать. Это про модель в MVC.
- как пользователь будет управлять системой. Это про контроллер.
- как пользователь будет получать состояние системы, результат действий своих и других пользователей. Это про виды.
Вы же пользуетесь АТС, но о ней не знаете. Она для вас прозрачна — вы набрали номер нужного человека и с ним разговариваете. Или лучше представьте себе разговор с человеком по скайпу — там и роутеры и инфраструктура провайдера и бекенд самого скайпа. Но вы не замечаете, что взаимодействуете с ними, вы разговариваете с конкретным лицом.
> Откуда у вас путаница?
Не у меня, а у вас.
> Апи модели состоит в рамках MVC из апи для изменения состояния модели, которым пользуются контроллеры, и апи для предоставления состояния модели, которым пользуется виды. Не механизм притворяется моделью, а модель предоставляет механизм, скрывая за ним свою внутреннюю сложность.
Для того, чтобы скрывать сложность, никакого механизма не надо, он тогда вообще лишний и его следует убрать. Механизм решает конкретную задачу роутинга, если вам ее решать не надо — к черту механизм, взаимодействуйте напрямую с моделью, прослойки только вызовут сильную связанность ваших компонент и повысят стоимость поддержки.
> Любое серьезное изменение требований сводится, вернее разбивается на параллельным изменения требованиям к модели, видам и контроллерам.
Так еще раз, требования к модели не берутся из воздуха. Их создает программист, который занимается реализацией, на основе требований аналитика, которые формулируются в терминах вида.
> Вот ничего тут о виде вообще.
Это о виде и только о виде.
> Это о модели в чистом виде.
Вы опять путаете MVC-модель и модель предметной области. Аналитик описывает требования к предметной области. Ответственность за выражение предметной области в MVC несет вид. У модели такой ответственности нет, она вообще не про то. Там может не быть ни корзины ни товаров, в рассматриваемом случае.
> что система должна делать, что хранить, что показывать. Это про модель в MVC.
Нет, это все про вид.
> как пользователь будет получать состояние системы, результат действий своих и других пользователей. Это про виды.
А вот это как раз про модель.
Вы же пользуетесь АТС, но о ней не знаете. Она для вас прозрачна — вы набрали номер нужного человека и с ним разговариваете. Или лучше представьте себе разговор с человеком по скайпу — там и роутеры и инфраструктура провайдера и бекенд самого скайпа. Но вы не замечаете, что взаимодействуете с ними, вы разговариваете с конкретным лицом.
Я замечаю, что разговариваю с конкретным лицом через какой-то механизм, который он мне предоставил, чтобы я мог ему сообщить что-то важное. Раз я разговариваю с ним через механизм и этот механизм определенно не часть меня, то при разговоре для меня механизм — это часть собеседника. Вплоть до проявлений недовольства собеседником в виде разбивания трубки о пол :)
Для того, чтобы скрывать сложность, никакого механизма не надо, он тогда вообще лишний и его следует убрать. Механизм решает конкретную задачу роутинга, если вам ее решать не надо — к черту механизм, взаимодействуйте напрямую с моделью, прослойки только вызовут сильную связанность ваших компонент и повысят стоимость поддержки.
Чтобы модель могла посылать сообщения виду — механизм должен быть. Будет он в виде захардкоженных вызовов методов вида или использования полноценной кластерной системы сообщения — деталь реализации.
Так еще раз, требования к модели не берутся из воздуха. Их создает программист, который занимается реализацией, на основе требований аналитика, которые формулируются в терминах вида.
Требования и к модели, и к виду, и к контроллеру создаёт не программист, а аналитик, архитектор. Программисты эти требования реализуют, часто параллельно, интегрируя реализации только когда всё уже готово. И если вдруг программист вида выясняет, что для реализации требований к виду ему чего-то не хватает в апи модели, он идёт не к программисту модели, а к аналитику или архитектору и тот, если реально поймёт, что провтыкал изменит требования к модели, а может и к виду.
Ответственность за выражение предметной области в MVC несет вид. У модели такой ответственности нет, она вообще не про то. Там может не быть ни корзины ни товаров, в рассматриваемом случае.
Ответственность за выражение бинарных данных в различных видах памяти системы в терминах предметной области несёт модель. Грубо, модель должна знать данные из какой таблицы базы данных надо отдать абстрактному программному клиенту, когда он просит список товаров в корзине. И в какую табличку что надо записать, когда абстрактный клиент просит добавить товар в корзину. Только она знает, что есть товар, а что есть корзина, напрямую из требований предметной области от аналитика.
И контроллер, и вид, оперируют теми и только теми терминами предметной области, которые им предоставляет модель. Так же они оперируют терминами UI типа окон, клавиш и мыши, о которых модель ничего не знает в общем случае. Это контроллер и вид могут ничего не знать о предметной области, служа тупой прокладкой между моделью и пользователем.
Нет, это все про вид.
Нет, это именно про модель. Фраза "пользователь может добавлять товары в корзину из каталога, после чего создать заказ" не содержит ни слова про вид или контроллер. Она не говорит как пользователь будет добавлять и создавать, например, вводить с клаиатуры команды и артикулы, или клацать мышью, нужно ли ему показывать каталог и корзину в процессе, чтобы он выбирал прямо из каталога и контролировал что уже есть в корзине.
А вот это как раз про модель.
А вот это как раз про виды. Отобразить пользователю список товаров в корзине в виде текстовой таблицы с названием товаров или в виде галереи картинок товаров, вывести кнопки удаления товаров из списка или нет — это ответственность вида.
При этом данный механизм максимально полностью передает АПИ диалога собеседника.
> Чтобы модель могла посылать сообщения виду — механизм должен быть.
Для этого достаточно метод вида дернуть. Это одно и то же.
> Требования и к модели, и к виду, и к контроллеру создаёт не программист, а аналитик, архитектор.
Требования к модели создает программист. Никто больше их просто создать не может, физически, так как модель — целиком и полностью деталь реализации. Кроме программиста никто о ней не знает, никто ее не наблюдает, никто с ней не взаимодействует и никто проконтролировать ее свойства не может.
> И если вдруг программист вида выясняет, что для реализации требований к виду ему чего-то не хватает в апи модели, он идёт не к программисту модели, а к аналитику или архитектору и тот, если реально поймёт, что провтыкал изменит требования к модели, а может и к виду.
Да не важно абсолютно, к кому он идет. Важно, что если вашему виду что-то надо, то модель придется изменить для выполнения этих требований.
> Грубо, модель должна знать данные из какой таблицы базы данных надо отдать абстрактному программному клиенту
Модель не знает ничего ни про БД ни про ее таблицы. Слой взаимодействия с БД выходит за рамки MVC, он сидит (если вообще есть) где-то за моделью.
> И в какую табличку что надо записать, когда абстрактный клиент просит добавить товар в корзину. Только она знает, что есть товар, а что есть корзина, напрямую из требований предметной области от аналитика.
Что такое товар и корзина знает вид, потому что к нему требования формируются в этих терминах. Он не может не знать. С другой стороны, еще раз, в модели вообще ни товаров, ни корзин может при этом не быть. Они точно будут в виде, но вот будут ли в модели — это вопрос открытый и сугубо решения самого программиста.
> И контроллер, и вид, оперируют теми и только теми терминами предметной области, которые им предоставляет модель.
Они оперирует теми терминами, в которых с ними взаимодействует пользователь. Он взаимодействует в терминах корзин и товаров. При этом в модели нет никаких корзин и товаров. В ней, например, блоб неизвестной для модели структуры (условимся так, давайте, чтобы вам было проще). Модель может отдавать этот блоб виду и применять к нему диффы, которые отсылает контроллер.
> Нет, это именно про модель. Фраза «пользователь может добавлять товары в корзину из каталога, после чего создать заказ» не содержит ни слова про вид или контроллер.
Про модель она тоже ни слова не содержит. Но тот программный модуль, который будет выполнять эту задачу в MVC — это будет вид. Не модель.
Для этого достаточно метод вида дернуть. Это одно и то же.
Дергание метода — стандартный механизм доставки сообщений от объекта к объекту в мэйнстрим ООП-языках.
Требования к модели создает программист. Никто больше их просто создать не может, физически, так как модель — целиком и полностью деталь реализации. Кроме программиста никто о ней не знает, никто ее не наблюдает, никто с ней не взаимодействует и никто проконтролировать ее свойства не может.
Требования к модели создаются тем, кто анализирует и декопомозирует требования к системе в целом, формируя на их базе требования к подсистемам. И создаются они непосредственно от требований к системе в целом, а не через требования к виду. Более того, требования к модели можно создавать не имея требований к видам и контроллерам, не имея ещё требований к тому как система будет взаимодействовать с пользователем, но имея требования того что она должна делать. Физически можно попробовать создавать сначала требования ко всем видам и контроллерам системы, а потом учитывая их создавать требования к модели, но это как создать требования к пульту управления, а потом на их базе создавать требования к телевизору, этим пультом управляемым. То есть в требованиях к пульту писать "при нажатии кнопки первого канала телевизор должен начать показывать изображение системы SECAM, принимаемое на частоте 49,75 МГц", а не "при нажатии кнопки первого канала пульт должен послать сигнал переключения на первый канал".
Да не важно абсолютно, к кому он идет. Важно, что если вашему виду что-то надо, то модель придется изменить для выполнения этих требований.
Если системе что-то надо показать пользователю, то это будет внесено в требования к виду и, может быть, в требования к модели. Разработчик вида для реализации своих требований не может менять требования к модели. Максимум, может инициировать повторную декомпозицию требований к системе. Даже если один человек занимается всем на проекте, то менять требования к модели он будет не в роли разработчика вида, а в роли разработчика системы в целом, пускай и исправляя ошибку, допущенному им при декомпозиции требований к системе в роли архитектора.
Модель не знает ничего ни про БД ни про ее таблицы. Слой взаимодействия с БД выходит за рамки MVC, он сидит (если вообще есть) где-то за моделью.
Именно модель и знает. В рамках MVC слой хранения в модели (именно "в", а не "за"). В MVC только три элемента.
Что такое товар и корзина знает вид, потому что к нему требования формируются в этих терминах. Он не может не знать.
К виду требования могут формировать в форме типа "вывести в правом верхнем углу первых 5 элементов из списка, полученного по вызову метода Model.getCartItems(), а вверху вывести результат вызова Model.getCartMethadata.name". ни капли информации о предметной области, только об API модели.
С другой стороны, еще раз, в модели вообще ни товаров, ни корзин может при этом не быть. Они точно будут в виде, но вот будут ли в модели — это вопрос открытый и сугубо решения самого программиста.
В виде может надпись "Корзина", но это лишь метка для визуального блока, заданного в требованиях вместо вызова Model.getCartMethadata.name. Если вид знает, что добавление товара в корзину приведёт к уменьшению количества доступных товаров в каталоге, то это протечка абстракции, для него корзина это просто список элементов, свойства которых нужно отобразить определенным образом.
Они оперирует теми терминами, в которых с ними взаимодействует пользователь.
Именно. Пользователь с ними оперирует терминами UI — окна, формы, кнопки, указатели мыши и т. п. Их задача транслировать эти термины в термины модели и обратно.
При этом в модели нет никаких корзин и товаров. В ней, например, блоб неизвестной для модели структуры (условимся так, давайте, чтобы вам было проще). Модель может отдавать этот блоб виду и применять к нему диффы, которые отсылает контроллер.
Именно в модели они и есть. Это контроллер преобразует действия пользователя типа нажатия кнопки "добавить в корзину" в блоб, который модель будет рассматривать как команду на добавление конкретного товара в корзину конкретного пользователя. Контроллер знает только правила преобразования действий пользователя в блоб и способ передачи этого блоба в модель. аналогично вид знает только способ показа пользователю полученного от модели блоба. Вид даже может знать, что рядом с каким-то элементом из списка в блобе надо отобразить кнопку "удалить из корзины" и повесить на неё обработчик контроллера, но он не должен знать, что после нажатия на кнопку придёт новый блоб от модели, в котором будет на один пункт меньше.
Про модель она тоже ни слова не содержит. Но тот программный модуль, который будет выполнять эту задачу в MVC — это будет вид. Не модель.
Вид будет только показывать текущие состояния каталога и корзины, а также элементы UI для ввода действий пользователя. Контроллер будет транслировать эти действия в сообщения модели типа "пользователь хочет добавить товар такой-то в корзину", "пользователь хочет очистить корзину", "пользователь хочет оформить заказ". Контроллер знает только как переводить с языка элементов UI, с языка физических действий пользователя типа нажатия кнопок на язык модели. А вид — переводчик с языка модели на язык элементов UI типа текстовых меток. Он знает лишь как при сообщении от модели "состояние корзины изменилось" отобразить новое состояние, может в конкретном окошке добавить ещё несколько текстовых меток и, может быть, изменить размер окна, а может быть добавить полосу прокрутки, а может быть вообще ничего не делать, если полоса прокрутки уже есть и изменилось что-то в невидимой область.
Модель будет давать виду текущие состояния каталога и корзины, модель будет при желании пользователя на добавление товара в корзину добавлять его (проверяя предварительно на возможность добавления) и осуществлять уменьшение остатков в каталоге. Знания о том, что нужно делать в ответ на те или иные желания пользователя — только в ней. Контроллер должен ли сообщить ей о таком желании, он должен знать как преобразовать действия пользователя в сообщение в терминах модели. Знания о том, что нужно сообщить о предметной области после исполнения желания пользователя — тоже только в модели. Вид должен только знать как преобразовать программное сообщение модели о состоянии предметной области во что-то, что пользователь может увидеть или услышать. По сути и вид, и контроллер осуществляют лишь перевод между пользователем и моделью с языка элементов UI на язык блобов, понятных модели, и обратно. Они — деталь реализации, позволяющие общаться пользователю и модели о чём-то известном им двоим. Вид и контроллер — динамик и микрофон в телефоне, а модель — всё остальное, если пользоваться анлогией с АТС.
Это требования системе в общем. Требования же к модели в MVC создает конкретно программист и никто кроме него, потому что остальные люди ни о каких MVC (и, с-но, ни о каких mvc-моделях) не в курсе. Вы упорно продолжаете путать модель предметной области и модель MVC. Это РАЗНЫЕ сущности, которые лишь МОГУТ БЫТЬ связаны.
> Физически можно попробовать создавать сначала требования ко всем видам и контроллерам системы, а потом учитывая их создавать требования к модели
Именно так все и поступают на практике, как мы уже выяснили на конкретных примерах.
> Именно модель и знает. В рамках MVC слой хранения в модели (именно «в», а не «за»). В MVC только три элемента.
MVC не описывает все систему. Он описывает только подсистему взаимодействия с пользователем (интерфейс), это его предназначение. Слой взаимодействия с хранилищем просто не включен в MVC, его там нет, он за пределами архитектуры.
> К виду требования могут формировать в форме типа «вывести в правом верхнем углу первых 5 элементов из списка, полученного по вызову метода Model.getCartItems(), а вверху вывести результат вызова Model.getCartMethadata.name».
Нет, так требования сформулировать нельзя, потому что нельзя будет их проверить.
> Если вид знает, что добавление товара в корзину приведёт к уменьшению количества доступных товаров в каталоге, то это протечка абстракции
Это не протечка абстракции, а необходимое условие того, что системой можно пользоваться.
> Именно в модели они и есть.
Еще раз, давайте сразу оговоримся — в нашей модели хранится блоб, структура которого модели неизвестна. Забудьте про знание моделью корзин и всего остального. Это знание только у вида и контроллера. Модель либо принимает диффы контроллера, либо передает дифф виду. Рассматриваем именно эту конкретную ситуацию, чтобы вам было проще, иначе вы путаетесь.
> но он не должен знать, что после нажатия на кнопку придёт новый блоб от модели, в котором будет на один пункт меньше.
Представьте себе, об этом _по отдельности_ не знает ни один из M, V, C! Только в совокупности. В этом и смысл разделения ответственности в MVC. Контроллер знает, что значит «нажать на кнопку», и что надо дернуть определенным образом модель (отослать дифф). модель ничего не знает про кнопки и окна, но знает, что когда получен дифф — надо оповестить об этом вид. Вид ничего не знает про кнопки и про диффы, но в курсе, что когда он получает новый блоб — надо перерендерить окно согласованно с новыми данными.
> Знания о том, что нужно делать в ответ на те или иные желания пользователя — только в ней.
В рассматриваемом случае в ней нет никаких знаний. Она просто транслирует данные от контроллера к виду, при этом даже будучи не в курсе структуры этих данных (хотя о структуре знает вид и контроллер).
> По сути и вид, и контроллер осуществляют лишь перевод между пользователем и моделью с языка элементов UI на язык блобов, понятных модели, и обратно.
Все верно, но именно к этим преобразованиям и формулируются требования. Нам совершенно плевать, что там в модели — блобы, корзины, есть ли там методы addToCart или просто голая трансляция данных неизвестной структуры (вы же не будете мне тут всерьез рассказывать о том, что в рассматриваемом случае у нас модель, которая блобы гоняет, действует «в терминах предметной области»? ну ведь правда не будете?). Нам важно чтобы пользователь мог заказать товар (о котором модель ничего не знает).
Это требования системе в общем. Требования же к модели в MVC создает конкретно программист и никто кроме него, потому что остальные люди ни о каких MVC (и, с-но, ни о каких mvc-моделях) не в курсе.
MVC — архитектурный паттерн. В курсе об MVC прежде всего архитектор, это программисты как раз о нём могут не знать. Архитектор говорит одному программисту разработать модуль A трансляции событий UI в команды модуля Б, второму разработать модуль В трансляции событий модуля Б в графические элементы, а третьему разработать модуль Б, который будет принимать (не важно от кого) по своему API команды, изменять по ним своё состояние и эмитировать для подписавшехся по его же API подписчиков событий об изменении состояния. Вот первому точка входа в модуль Б и список команд (часть API модуля Б), вот второму точка регистрации листенеров и список событий (вторая часть API модуля Б), а третьему обе части и описание бизнес-процессов от аналитика. Все трое даже не знают, что реализуется MVC, но он реализуется и не случайно.
Именно так все и поступают на практике, как мы уже выяснили на конкретных примерах.
Вот никак не выяснили. Я сказал, что такое может быть, но это как гланды не через рот удалять в общем случае.
MVC не описывает все систему. Он описывает только подсистему взаимодействия с пользователем (интерфейс), это его предназначение. Слой взаимодействия с хранилищем просто не включен в MVC, его там нет, он за пределами архитектуры.
Описывает. Как-то так:
- V — показывает данные пользователю
- С — обрабатывает действия пользователя на UI
- M — вся остальная система, предоставляющая свой API для V и C
Туда входит и слой хранения данных, если он есть.
Нет, так требования сформулировать нельзя, потому что нельзя будет их проверить.
Легко и просто. При модульном тестировании — просто эмулируя API модели. При интеграционном — создав фикстуры для модели или иным образом программно приведя её в тестируемое состояние. При приёмочном — выполняя полный цикл действий пользователя по приведению модели в тестируемое состояние.
Это не протечка абстракции, а необходимое условие того, что системой можно пользоваться.
Это протечка абстракции по MVC. Вид вообще не должен знать, что может изменить состояние системы, его дело только вовремя эти изменения отображать. Контроллер не должен знать, к каким изменениям приводят действия пользователя и приводят ли, его дело только сообщать о действиях пользователя системе, скрывая детали UI-реализации. Остальная система, модель, знает как менять своё состояние при реакции на те или иные команды от C и как оповещать об изменениях V, которые хотят об этих изменениях знать. Системе всё равно есть ли UI или с ней программные общаются, ей поступила команда "добавить в корзину пользователя А товар Б" и если условия добавления соблюдаются, она добавляет и всем заинтересовавшимся, если они есть, сообщает "в корзину пользователя А добавлен товар Б". Ей всё равно, нажал пользователь кнопку в окне браузера, введена команда в консоли или запущена отдельная программа с единственной функцией дать команду модели "добавить в корзину пользователя А товар Б".
Еще раз, давайте сразу оговоримся — в нашей модели хранится блоб, структура которого модели неизвестна. Забудьте про знание моделью корзин и всего остального. Это знание только у вида и контроллера. Модель либо принимает диффы контроллера, либо передает дифф виду. Рассматриваем именно эту конкретную ситуацию, чтобы вам было проще, иначе вы путаетесь.
Это вы описываете приложение по работе с блобами, в котором пользователь редактирует блобы, держа всю их семантику при себе? Редактор блобов какой-то? Только в этой ситуации, по-моему, можно говорить одновременно о MVC и посылке диффов от контроллера в модель. Но при этом и контроллер с видом должны воспринимать блоб как блоб.
Если контроллер и/или вид, знают, что, например, этот blob — application/json представление корзины пользователя, то это не MVC. Это приложение, в котором есть тупое хранилище, а бизнес-логика не отделена от UI, пускай и разделена на модуль вывода и модуль ввода.
Представьте себе, об этом по отдельности не знает ни один из M, V, C! Только в совокупности. В этом и смысл разделения ответственности в MVC. Контроллер знает, что значит «нажать на кнопку», и что надо дернуть определенным образом модель (отослать дифф). модель ничего не знает про кнопки и окна, но знает, что когда получен дифф — надо оповестить об этом вид. Вид ничего не знает про кнопки и про диффы, но в курсе, что когда он получает новый блоб — надо перерендерить окно согласованно с новыми данными.
Да, так оно примерно и работает в MVC-приложении по редактированию блобов. Если вид знает, что этот блоб это json представление корзины пользователя, а контроллер знает, что по нажатию кнопки ему надо отправить дифф джсона, в котором на один товар будет больше, то это MVC-магазин.
Все верно, но именно к этим преобразованиям и формулируются требования.
Требования к виду формулируются на языке элементов UI и языке модели. Требования к контроллеру — на языке событий UI и языке модели. Требования к модели — на языке модели. Ели модель знает только про блоб, то требования к контроллеру и виду формируются только на языке элементов и событий UI и на языке блобов.
Нет, не описывает. MVC описывает слой интерфейса и только его. M — это никакая не «вся остальная система», давайте без отсебятины. Это вполне конкретная часть системы с вполне конкретным функционалом, в который слой взаимодействия с хранилищем не входит.
> MVC — архитектурный паттерн. В курсе об MVC прежде всего архитектор
Ну так архитектор — это программист и есть.
> Легко и просто. При модульном тестировании — просто эмулируя API модели. При интеграционном — создав фикстуры для модели или иным образом программно приведя её в тестируемое состояние.
Интеграционное тестирование с фикстурами вы никак не проведете, только модульное. А на приемочном у вас будут требования к виду, так как модели там нет и она не проверяется.
> Это протечка абстракции по MVC. Вид вообще не должен знать, что может изменить состояние системы, его дело только вовремя эти изменения отображать.
Это чисто теоретические рассуждения. На практике невозможно построить систему, в которой вид не знает о модели и наоборот. Она либо будет плохо работать, либо вы получите очень сильную связанность, и это будет неподдерживаемо.
> Это вы описываете приложение по работе с блобами, в котором пользователь редактирует блобы, держа всю их семантику при себе?
Нет, это приложение, в котором пользователь формирует корзину товаров и отправляет заказ.
> Если контроллер и/или вид, знают, что, например, этот blob — application/json представление корзины пользователя, то это не MVC.
Конечно же, это MVC. С чего бы нет? Все элементы функционируют согласно своим определениям.
> Если вид знает, что этот блоб это json представление корзины пользователя, а контроллер знает, что по нажатию кнопки ему надо отправить дифф джсона, в котором на один товар будет больше, то это MVC-магазин.
Все верно. При этом модель не знает, что там за диффы и что за блобы. Она просто в нужный момент обновляет данные внутри себя и передает новые данные виду, все.
> Требования к модели — на языке модели.
Требования к модели вообще никак не оформляются, потому что их нет. Все требования к системе исчерпываются требованиями к виду и контроллеру. Требование к модели на уровне описания бизнес-процессов просто _невозможно сформулировать_. Не сможете вы просто придумать такое требование, чтобы это было требованием к модели, а не к интерфейсу.
> Ели модель знает только про блоб, то требования к контроллеру и виду формируются только на языке элементов и событий UI и на языке блобов.
Это вы уже свое что-то выдумываете. Еще раз — требование к виду/контроллеру это предоставить пользователю возможность собрать и отправить корзину. Никаких окошек и кнопочек там вовсе нет. Программист (пусть уровня архитектора, окей) решил, что у него будет MVC, при этом модель хранит состояние в виде блоба, получая его диффами от вида и транслируя модели.
Почему он так сделал? Да потому что пользователь — это внешний сервис, который обменивается информацией с вашей системой по бинарному протоколу, ему тупо удобнее кидать туда-сюда блобы и реализовывать методы вида addToCart было бы просто ненужным оверхедом.
Нет, не описывает. MVC описывает слой интерфейса и только его. M — это никакая не «вся остальная система», давайте без отсебятины. Это вполне конкретная часть системы с вполне конкретным функционалом, в который слой взаимодействия с хранилищем не входит.
Именно, MVC — это паттерн организации UI, в котором вид отвечает за трансляцию данных модели в вид, доступный пользователю, контроллер за трансляцию пользовательских действий, направленных на изменение данных модели в вид, понятный модели, а собственно обработка действий пользователя и предоставление их результатов для отображения ему — в модели. За каким из этих слоев может находиться слой персистентности? За контроллером и видом не может, они отвечают за ввод и вывод пользователя, не более.
Ну так архитектор — это программист и есть.
Нет. Архитектор — постановщик задач программистам. Архитектор может иногда писать код, но в это время он выступает в роли программиста. Программисты могут писать код без архитектора, только если сначала выступают сами для себя в роли архитектора.
Интеграционное тестирование с фикстурами вы никак не проведете, только модульное. А на приемочном у вас будут требования к виду, так как модели там нет и она не проверяется.
Как это не проведу? Псевдокод:
DB.clearData
DB.insert('fixtures.sql')
model = new Model(userId = 1)
view = new SomeView(model = model)
view.show()
assertInvoked(model.addCartEventListener, listener = view)
assertInvoked(model.getCartItems, userId = 1)
model.addCartItem(goodsId = 1)
assertInvoked(view.handleCartEvent, type = 'itemAdded', goodsId = 1)
Это чисто теоретические рассуждения. На практике невозможно построить систему, в которой вид не знает о модели и наоборот. Она либо будет плохо работать, либо вы получите очень сильную связанность, и это будет неподдерживаемо.
Вид должен знать нужную ему часть API модели, должен знать как ему получать нужные ему данные из модели. Для модели же в худшем случае, вид — это клиент, который подписался по её API на сообщения об изменении стейта, и которому она о них сообщает. максимум что она знает о нём — это то, что реализует её интерфейс подписки на события. И это как раз уменьшает связанность. Это цель отделения в MVC M от VC — отсутствие зависимости модели, ядра приложения от его UI: пока разработчики ядра не изменяют API модели, они могут как угодно менять логику этого ядра. Могут прятать за моделью фикстуры, могут эмулятор адронного коллайдера, могут сам адронный коллайдер, не то что работу интернет-магазина
Нет, это приложение, в котором пользователь формирует корзину товаров и отправляет заказ.
Значит это или не MVC UI, или вы называете моделью слой хранения, не подозревая, что на самом деле модель у вас в каком-то адаптере вашей модели для вью и контроллера, который трансформирует блобы в товары в корзине и обратно. Из оригинального определения MVC (1979): "Models represent knowledge. A view is a (visual) representation of its model." Знания (предметной области) в модели, вид лишь визуализирует их. Модель должна знать, например, что нельзя помещать в корзину товар, которого нет на складе, какой бы "дифф" ей не прислал контроллер. И должна послать виду каталога сообщение о том, что на один товар стало меньше, а виду корзины, что на один больше.
Требования к модели вообще никак не оформляются, потому что их нет. Все требования к системе исчерпываются требованиями к виду и контроллеру. Требование к модели на уровне описания бизнес-процессов просто невозможно сформулировать. Не сможете вы просто придумать такое требование, чтобы это было требованием к модели, а не к интерфейсу.
MVC про UI, где требования к M — это требования к её API, её контракту, её пред- и пост-условиям, её инвариантам. Грубо, "вызов метода cartModel.addItem(itemId) должен выбрасывать исключение, если на складе нет доступных товаров c этим id." Чистое требование к модели, которое можно проверить и без наличия V и C, хоть модульным тестированием, застабив склад, хоть интеграционным, занеся в базу нулевое количество.
Это вы уже свое что-то выдумываете. Еще раз — требование к виду/контроллеру это предоставить пользователю возможность собрать и отправить корзину.
Это требование к системе в целом. Из них в требования к модели входит примерно такие действия:
- хранить и выдавать список доступных товаров на складе
- хранить, выдавать и изменять корзину пользователя, при этом при изменениях менять остатки на складе, не допускать отрицательных остатоков, не допускать отрицательных количеств в корзине, не допускать превышения суммы заказа предела установленного ЦБ РФ для наличного расчёта и прочая, и прочая, и прочая.
Да потому что пользователь — это внешний сервис, который обменивается информацией с вашей системой по бинарному протоколу.
Какое это имеет отношение к MVC? Программные системы взаимодействуют по API, а MVC это про UI, про human–computer interaction, man–machine interface
За моделью, конечно. Я же об этом и сказал. Смысл том, что в саму модель он не входит. Он где-то _за_ ней.
> Нет. Архитектор — постановщик задач программистам.
Тогда архитектор ничего не знает про MVC. А решение о том, что: «мы будем использовать mvc» — это уже роль программиста, в чистом виде.
> Как это не проведу? Псевдокод:
Ну вот ваш псевдокод — это модульный тест. Вы не проверили интеграцию. Любой assertInvoked гарантирует, что вы заменяете тест интеграции модульным. В интеграционных текстах никогда нету assertIvoked.
> Вид должен знать нужную ему часть API модели, должен знать как ему получать нужные ему данные из модели.
Дело даже не в знании. Дело в том, что модель ВСЕГДА проектируется с учетом нужд вида. Иначе будет кусок чего-то вонючего вместо модели, работать с которой потом будет невозможно.
> Это цель отделения в MVC M от VC — отсутствие зависимости модели, ядра приложения от его UI
Проблема в том, что чем дальше вы идете в использовании этого принципа, тем сильнее гвоздями прибиваете вашу модель и вид друг к другу и, с-но, усложняете дальнейший процесс разработки.
> Из оригинального определения MVC (1979): «Models represent knowledge. A view is a (visual) representation of its model.»
Ну вот в данном случае мы это и имеем. Модель предоставляет блоб с данными (знания), а вид — их преобразует к понимаемому пользователем формату. И никто от модели MVC не требует знать структуры этих данных.
> MVC про UI, где требования к M — это требования к её API, её контракту, её пред- и пост-условиям, её инвариантам.
Но это все требования у УИ. Все требования к системе — это требования к поведению УИ.
> Грубо, «вызов метода cartModel.addItem(itemId) должен выбрасывать исключение, если на складе нет доступных товаров c этим id.»
А такого требования не будет. Будет требование: «в случае попытки добавления в корзину отсутствующего на складе товара, пользователь должен получить отказ». Это требование к виду, не к модели.
> Это требование к системе в целом.
А требование к системе в целом — это всегда требование к слою взаимодействия с этой системой. О чем я вам и талдычу. Ну попробуйте сформулировать требование, которое было бы требованием к модели, а не уи.
> Какое это имеет отношение к MVC?
Совершенно прямое. Нет никакой разницы является ли вам интерфейс интерфейсом между двумя сервисами или между человеком и компьютером. Это в обоих случаях одинаково MVC.
За моделью, конечно. Я же об этом и сказал. Смысл том, что в саму модель он не входит. Он где-то за ней.
Входит в рамках MVC. В них просто ничего нет кроме модели, вью и контроллера. Отдельного слоя может и не быть, модель прямо с базой сожет работать. Всё приложение, кроме UI абстрагируется за API модели.
Тогда архитектор ничего не знает про MVC. А решение о том, что: «мы будем использовать mvc» — это уже роль программиста, в чистом виде.
Это роль архитектора. MVC — архитектурный паттерн.
Любой assertInvoked гарантирует, что вы заменяете тест интеграции модульным.
Интеграционные тесты проверяют интеграцию модулей. В данно псевдокоде тестируется интеграция модели и вью. Проверяется взаимодействие модулей. В юнит-тестах я бы просто замокал один из модулей.
Дело даже не в знании. Дело в том, что модель ВСЕГДА проектируется с учетом нужд вида. Иначе будет кусок чего-то вонючего вместо модели, работать с которой потом будет невозможно.
С учётом, да. Если приложение в целом должно что-то показать, то это модель должна отдавать через свой API. Но если виду хочется, например, чтобы модель отдавала ФИО в нескольких форматах, то это попытка нарушить границы ответственности. Модель отдасть полное ФИО, а уж вид будет преобразовывать в отдних случаях в Иванов И. И., а в других в уважаемый Иван Иванович, ещё и пол из модели проанлизировав.
Проблема в том, что чем дальше вы идете в использовании этого принципа, тем сильнее гвоздями прибиваете вашу модель и вид друг к другу и, с-но, усложняете дальнейший процесс разработки.
Да как же прибиваю, если модель знает о виде только то, что он реализует интерфейс, который является частью API модели?
Ну вот в данном случае мы это и имеем. Модель предоставляет блоб с данными (знания), а вид — их преобразует к понимаемому пользователем формату. И никто от модели MVC не требует знать структуры этих данных.
Не путайте данные и знания. Знания это как раз понимание формата данных. Если у нас приложение корзины с товаром, то то знания о том, что такое корзина и товар должна находиться в модели. Разработчик, который хочет понять, например, чем в приложении товар отличается от товарной позиции должен лезть в модель. В вид разве что заглянуть посмотреть какой метод модели дергается для отображения рядом с меткой "товар", а какой "товарная позиция". Более того, в модели могут быть знания, которые в виде никак не отображаются. Например, что в каталоге не отображаются товарные позиции с нулевым остатком.
Но это все требования у УИ. Все требования к системе — это требования к поведению УИ.
Нет. UI — это механизм взаимодействия системы и пользователя, а не система. Банально в требованиях к UI не напишешь "при нажатии на кнопку должно отправляться письмо менеджеру". UI не оперирует понятием "отправка письма", это понятие совсем другого уровня по сравнению с экранами, окнами, мышами и т. п.
А такого требования не будет. Будет требование: «в случае попытки добавления в корзину отсутствующего на складе товара, пользователь должен получить отказ». Это требование к виду, не к модели.
Это требование к системе. Оно будет декомпозировано на требования к модели "должно бросить исключение ZeroResidue при попытке добавления товара с нулевым остатком" и на требование к UI вывести сообщение об ошибке "Нулевой остаток" при появлении исключения ZeroResidue. Кстати, именно в модели будут знания о том, что такое нулевой остаток, в каких условиях он может появиться. И вполне может оказаться, что эти условия будут меняться, если бизнес, например, решится на оверселл. К UI требование не остатки проверять, а показывать сообщения при исключении в модели, не более.
А требование к системе в целом — это всегда требование к слою взаимодействия с этой системой. О чем я вам и талдычу. Ну попробуйте сформулировать требование, которое было бы требованием к модели, а не уи.
Слой взаимодействия системы с внешним миром не только UI. Пример с письмом я уже приводил — это взаимодействие с SMTP сервером, а не пользователем. Ещё может быть требование "записать данные о продаже в базу данных учётной системы", "отправить чек в систему фискальной службы" и т. п. Сложные системы взаимодействуют не только с пользователем, но и с другими системами. Есть действия, которые в UI вообще не отображаются, типа сбор данных которые сейчас никак не обрабатываются, просто пишутся в базу на всякий случай, например по требованию закона или на перспективу внедрения анализа пользовательского поведения. У вас систему не примут, если приемщик сделает SQL запрос и не увидит там, например, IP с которогоо был заказа, хотя он нигде в системе не показывается.
Совершенно прямое. Нет никакой разницы является ли вам интерфейс интерфейсом между двумя сервисами или между человеком и компьютером. Это в обоих случаях одинаково MVC.
MVC — это про пользовательские интерфейсы, не про программные. Можно пробовать применять, но это использование не по назначению, другие методы организации API скорее всего будут намного более эффективны.
Это ваша выдумка. Давайте будем понимать под MVC то, что под ним принято понимать, а не выдумки? Есть ведь статьи, в которых есть описание. Вы сами на эти статьи ссылались. А теперь несете отсебятину. Зачем?
> Отдельного слоя может и не быть, модель прямо с базой сожет работать.
Может и работать. Может вообще все во дну кучу быть свалено. Но в MVC в ответственность модели не входит работа с хранилищем, вот и все.
> Это роль архитектора. MVC — архитектурный паттерн.
MVC — это деталь реализации. Решение о применении MVC принимает программист, потому что принятие такого решения — часть непосредственно написания программы. Это задача программиста. Вы, конечно, можете называть его как угодно, архитектором или еще как.
> Да как же прибиваю, если модель знает о виде только то, что он реализует интерфейс, который является частью API модели?
Все верно, чем меньше знает Х об Y тем сильнее он фактически оказывается к нему прибит и тем больше изменений приходится костылить в X, когда меняется. Именно по-этому при проектировании взаимодействующих модулей оно всегда делается с учетом этого взаимодействия — чтобы модули друг о друге знали и, т.о., могли быть в некоторой мере независимы.
> Не путайте данные и знания. Знания это как раз понимание формата данных.
В процитированном вами тексте ничего не было про знание в смысле понимания формата данных и прочие подобные вещи. Без отсебятины.
> В юнит-тестах я бы просто замокал один из модулей.
Так вы и замокали, по-этому у вас юнит-тест получился. В интеграционных assertInvokov не бывает.
> Но если виду хочется, например, чтобы модель отдавала ФИО в нескольких форматах, то это попытка нарушить границы ответственности.
Это уже несущественные детали отображения. Важно то, что если вид требует выводить информацию о пользователе, то модель его должна предоставить, вот и все. По-этому модель учитывает нужды вида, иначе потом это нельзя использовать. Например, вы будете эти данные пользователя получать какими-то кусками при помощи АПИ модели, которое вообще-то предназначено для каких-то других задач. Что будет адъ и содомия.
> Нет. UI — это механизм взаимодействия системы и пользователя, а не система.
А любые требования к системе и формулирются исключительно в виде описания взаимодействия с пользователем. Как иначе-то?
> Банально в требованиях к UI не напишешь «при нажатии на кнопку должно отправляться письмо менеджеру».
Как это? Именно так и напишешь. Почему нет?
> UI не оперирует понятием «отправка письма», это понятие совсем другого уровня по сравнению с экранами, окнами, мышами и т. п.
УИ оперирует теми терминами, которыми оперирует пользователь. Если пользователь подразумевает отправку письма, то УИ оперирует терминами отправки письма. УИ общается с пользователем в понятных ему терминах, иначе это провал, знаете ли, в виде очень, ОЧЕНЬ плохого УИ.
> Это требование к системе. Оно будет декомпозировано на требования к модели «должно бросить исключение ZeroResidue при попытке добавления товара с нулевым остатком»
То, что кто-то будет куда-то пробрасывать исключение — деталь реализации, которая находится в ответственности программиста.
То есть, еще раз — аналитик формулирует требования ко взаимодействию системы с пользователем (то есть, к УИ). Потом программист (ну или если хотите называйте его архитектором) уже преобразует это к требованиям для конкретных модулей системы. Если выбрана MVC то требования аналитика впрямую преобразуются к требованиям вида/контроллера MVC, на основе этих требований к виду контроллеру формируются требования к модели (и, возможно, что всем или части этих требований модель _уже_ удовлетворяет). И потом приступаем к реализации.
> Слой взаимодействия системы с внешним миром не только UI.
Любое взаимодействие с внешним миром — это UI, по определению. И передача данных по бинарному протоколу, и отправка сообщений по почте и все остальное. Есть система, есть внешний мир, есть интерфейс, при помощи которого ваша система и внешний мир теребят друг друга.
> MVC — это про пользовательские интерфейсы, не про программные. Можно пробовать применять, но это использование не по назначению, другие методы организации API скорее всего будут намного более эффективны.
Ну как, вот есть у вас сервер, который принимает хттп-запросы и отдает хтмл-страницы. Это классическое MVC, вы хотите сказать, оно тут «не по назначению»?
Нет, сообщение, механизм подписки и механизм доставки — это API модели. Как, например, интерфейсы DOM Event, EventTarget и EventListener — это API DOM, а не API вашего кода. Чтобы получать события DOM вы должны вызвать метод EventTarget.addEventListener(string type, EventListener obj), где type интересующие вас события, а obj — ваш объект, реализующий интерфейс DOM EventListener.
EventListener — это чей API, DOM или вашего кода? Знаете вы способы указать DOM использовать не метод handleEvent, а ваш invokeEventHandler, передавать ему не Event, а массив?
Как по мне, то это API DOM. Он диктует как подписаться на события, в частности какие колбэки он примет и как будет их использовать для оповещения. Это API DOM, этого его контракт, изменить который разработчики софта, работающего с DOM могут только как-то повлияв на требования к системе в целом на уровне W3С, WHATWG и т. п. Спихнуть неработоспособность вашей системы на то, что DOM при событиях упорно вызывает handleEvent и передаёт ему Event, а не вызывает ваш invokeEventHandler и не передаёт ему массив с вашей структурой не получится, пока вы не измените требования к реализациям DOM, пока не измените стандарт DOM, да и в этом случае сомнительно, что получится. Ну, если вы разработчик GMail у вас есть некоторые шансы, что в одном из основных браузеров DOM таки будет присылать события так вам хочется, но даже в этом случае я бы не стал на них рассчитывать.
Если модель дана как есть и какие-то EventEmitter'ы встроить не можем, то построил бы параллельное модели дерево прокси-объектов, фиксирующих вызов мутаторов модели, чтобы иметь после вызова мутатора полный список затронутых нод, чтобы перерендерить нужные виды.
Прокси-объекты как раз и будут как и определять затронутые ноды модели, так и хранить ссылки на виды, которые нужно перерендерить при затрагивании той или иной ноды.
Совсем не то же самое. У вас хардкорд, а я о динамическом, в рантайме определении вью, интересующихся той или или нодой модели.
Добавляем новый "геттер" в модель, дергаем его из вью. В вашей схеме ещё какой-то код надо будет писать?
Ну, собственно это классическая реализация MVC:
- виды получают информацию из модели и подписываются на интересующие их события об её изменении и изменяют представляемую пользователю информацию по ним без ведома контроллера
- контроллер транслирует события UI в сообщения (в мейнстрим ООП — вызовы мутирующих методов) модели без ведома вида
- модель знает о контроллере только как о клиенте, который просит её изменить своё состояние, а о виде только как о клиенте, который просит у неё информацию о состоянии и информацию об его изменении. С контроллером отношения у неё чисто как у функции с вызывающим кодом, а с видом, как у публикатора событий с подписчиком. Ничего об их устройстве или апи она не знает.
Вид в общем случае не знает о наличии мутирующих методов у модели, контроллер не знает о наличии геттеров и механизма уведомления у модели, модель не знает о наличии контроллера и вида в принципе, для неё это просто абстрактные клиенты её апи, которые общаются с ней по её правилам.
Что это за захардкоженные хэндлеры у вас в контроллере, что о них знают модель и вью, почему вид с моделью не работает напрямую, я не очень понимаю.
Если у вас вид будет работать с моделью напрямую, то она будет зависеть от неё, и это не MVC
А можно пруф? На той же википедии я не нашел нигде инфы, что контроллер должен быть прослойкой. Мне последнее время кажется, что это изобретение пхпшников. Вот к примеру:
Представление отвечает за получение необходимых данных из модели и отправляет их пользователю
Там же, на википедии:
Среднестатистический ТТУК (контроллер) получал данные из БД (используя уровень абстракции базы данных, делая вид, что это модель) или манипулировал, проверял, записывал, а также передавал данные в Представление. Такой подход стал очень популярен потому, что использование таких контроллеров похоже на классическую практику использования отдельного php-файла для каждой страницы приложения
А также:
Контроллеры же, — как элементы информационной системы, — ответственны лишь за:
— приём запроса от пользователя;
— анализ запроса;
— выбор следующего действия системы, соответственно результатам анализа
Нигде не сказано, что контроллер каким-то образом выступает как прослойка между моделью и вьюшкой.
Клиент зависит от сервиса, а не наоборот. Если я в виде пишу user.getFullname() то это вид зависит от модели, а не наоборот.
Да, встречал реализации, где контроллер выступает источником событий обновлений для вью, либо сам подписываясь на изменения модели, либо зная, какие её методы на что могут повлиять. Имеет право на жизнь, особенно первый вариант.
Не понимаю зачем отслеживать изменения в видах. Если даже есть у вида какой-то свой стейт, то это его исключительно внутренний стейт.
А про модель я сказал — прокси-объекты, учитывающие обращения к свойствам и методам каждой ноды дерева модели из вида. При условии, что сами ноды модели в наших интересах менять не можем.
Это тогда не mvc. В mvc вид и модель обязательно работают именно друг с другом, а контроллер с видом вообще может никак не взаимодействовать.
> У меня он не только терминальный. Я и на Модель и на Вид имею свой вид:)
Вы можете придумать любую архитектуру и назвать ее, например, «babylon-MVC». Но если вы говорите просто «MVC», то извольте использовать общепринятое определение, иначе вас просто люди не поймут.
Я использую rx и счастлив, в целом то.
Буквально вчера услышал от одного такого адепта Rx, у которого "нет проблем ни с Rx, ни с Angular", когда обратился к нему за помощью: Не знаю что делает shareReply, но Rx надо использовать по минимуму, тогда не будет этих проблем. Ну ничего, пол дня поковырялся в этой головоломке и таки разобрался. Оказалось, что из-за того, что у shareReply не было сабсрипшенов, то события пролетали в /dev/null не доходя до него. А когда сабскрипшены появлялись, то событиям в стриме уже было не от куда взяться.
Типичный костыль с Rx сейчас у меня выглядит так:
.pipe(
// в случае ошибок стрим разрушается, поэтому ошибки передаём как данные и везде проверяем
// нас интересуют изменения не всех полей, а конкретных
map( (data) => ( data instanceof Error ) ? data : { foo : data.foo , bar : data.bar } ) ,
// игнорируем обновления, если интересные нам поля не изменились
distinctUntilChanged( ( a, b )=> JSON.stringify(a) === JSON.stringify(b) ) ,
// игнорируем множественные последовательные обновления
debounce(0) ,
// не исполняем стрим с самого начала для каждого нового подписчика
shareReplay(1) ,
)
Это как раз неверно. Если rx используется — следует использовать его максимально (или вообще не использовать), иначе как раз и будет куча граблей.
> Оказалось, что из-за того, что у shareReply не было сабсрипшенов, то события пролетали в /dev/null не доходя до него. А когда сабскрипшены появлялись, то событиям в стриме уже было не от куда взяться.
shareReplay(n) эмитит последние n значений при подписке. Вы либо просто не прочитали описание функции, либо что-то выдумываете. Либо под дурочка косите.
> Типичный костыль с Rx сейчас у меня выглядит так:
Если он у вас типичный, то в чем проблема выделить его в отдельную пайп-функцию и горя не знать?
mayorovp я всего лишь решаю проблемы, которые давно решены в $mol_atom, MobX и куче других библиотек, но услужливо рабросаны по всем Rx-у.
Это как раз неверно. Если rx используется — следует использовать его максимально (или вообще не использовать), иначе как раз и будет куча граблей.
Да там человек топит за redux. Мол, Засунем весь стейт в глобальную переменную, обложимся экшенами и на каждый чих будем синхронно пушить состояние во все компоненты. А я только впендюрил ChangeDetection.OnPush
везде, чтобы этого не происходило, ибо всё страшно тупило.
shareReplay(n) эмитит последние n значений при подписке. Вы либо просто не прочитали описание функции, либо что-то выдумываете. Либо под дурочка косите.
Очень легко строить из себя умного, когда вам расписали пробелему и почему она происходит. Вот инвестигировать её не так-то просто, когда после рефакторинга тесты начинают валиться со странными сообщениями. И проблема тут не в 3 тупых головах, а в инструменте, который вместо того, чтобы гарантировать инварианты над состояниями, заставляет играть в railroad tycoon прыгая между файлами и засекая кто когда подписывается и кто когда отправляет данные.
Если он у вас типичный, то в чем проблема выделить его в отдельную пайп-функцию и горя не знать?
Типичный, да с вариациями. Да и цена этих костылей — снижение производительности. Я пока удерживаю себя от соблазна позаворачивать все 100500 операторов в подобные обёртки.
Не надо строить из себя умного, надо просто читать документацию. Если используете ф-ю — ну прочитайте как она работает. Тогда не будет неожиданностей. Ведь то, о чем вы пишете, это не какие-то специфические особенности потрохов, на которые вы случайно наткнулись — это базовое поведение.
Вы выдумываете какие-то мокрые мечты о том, как что-то должно работать (причем мечты неконсистентные), и потом обвиняете кого-то в том, что ф-я ведет себя корректно вместо того, чтобы быть сломанной. Ну это что-то совсем странное, согласитесь.
Или хотя бы подумали, в каких случаях может потребоваться, чтобы память текла по умолчанию.
> Или хотя бы подумали, в каких случаях может потребоваться, чтобы память текла по умолчанию.
Никакая память не течет, не выдумывайте. Все подписки освобождаются, как и должно быть.
А, ну раз вы так сказали, то никаких утечек нет, ага.
До чего фронтендеры докатились. Уже утечки памяти считают нормальным явлением. И правда, кому могут потребоваться приложения, которые могут работать дольше полу часа без перезагрузки?
Зачем вы врете? Там нет никаких утечек. Если бы использование методы действительно давало утечки, уже бы был сделан фикс.
Утечка — это когда у вас есть ресурс, который висит и который нельзя освободить (например, память которую вы выделили, и на которую просрали указатель). В рассматриваемом случае вам никто не мешает все требуемые подписки закрыть. Если вы, конечно, САМИ этого не сделали — то будет утечка, но кто кроме вас виноват? Вы бы еще new назвали утечкой само по себе.
> Затем, что вы утверждаете, что в 4 поведение было ошибочным и в 5 его «починили».
Все так и есть. В 4 поведение было ошибочным. Метод не должен съедать буфер и не должен закрывать подписку сабжекта на оригинальный обсервабл, потому что это подписка вообще вне его юрисдикции. Было бы поведение правильным и ожидаемым — никто бы его не менял.
Утечки по умолчанию могут быть разумным решением, когда в типовых случаях использования предотвращение утечек стоит дороже чем утечки.
Проблема в том что в мире современной разработки в среднем по индустрии возраст разработчика 25-29 лет, а стаж от 2-5 лет. Из личного опыта поиска сотрудников, в мире фронтэнда, в среднем можно сбросить пару лет стажа и возраста (https://insights.stackoverflow.com/survey/2016)
Как итог, большинство ни чего не знают о Макконэле и Мартине, не знают как работает железо, не понимают как работает браузер, путают понятия асинхронности, и конкурентности, не знают что такое «процесс», понятия не имеют о том что такое SmallTalk, не говоря уже о том факте что проблемы действительно сложных интерфейсов решались и были так или иначе решены еще до их рождения. Хуже того, не интересуются историей IT, фундаментальным Computer Science и им не интересно опираться на опыт поколений. На самом же деле, некоторые интерфейсы распространенных приложений в 90-тых уже тогда были куда сложнее современных web-интерфейсов.
Большинство опираются исключительно на библию своих проповедников и маркетинговые уловки (да в OpenSource тоже есть маркетинговый bullshit, когда под видом конфетки продают не пойми что), меняя проповедников каждые пару лет.
На первую полосу у них почему то выходит не решение проблем людей и бизнеса, а fun, emoji в консоли и hover анимации. Кстати, не стоит забывать что тот же Фейсбук в большинстве своем разрабатывает поколение Фейсбук, а Цукерберг был обычным PHP-шником которых гнобят и хоронят за их «говнокод» уже второй десяток лет (Хотя проблема понятное дело не в языке, проблема в комьюнити и индустрии). Конечно как и везде, в Facebook есть хорошие инженеры. c'est la vie.
Из личного же опыта Redux в итоге вызывает больше проблем, и не решает то о чем заявлено. Причем не у меня лично а у коллег фронтэндщиков, которые привели его в проект примерно так: "- Ура, мы начинаем новый проект, теперь мы сделаем все правильно, реактивно, и офигенно. У нас будет сложная админка, а значит нам срочно нужен React и Redux. Собирать все это будет webpack и мы еще прикрутим иммутабельность и реактивность". Как итог, изменение требований к более или менее средней форме или куску интерфейса занимает несколько недель и то и месяц разработки (в попытках понять, а как же это сделать на Redux + React + Saga + Thunk + Redux Form +… «правильно» и чтобы работало. Разработка превращается в забивание костылей, потому что «так не работает», вот так «нарушен источник правды», ну а так «в реакте не принятно»), а разобраться в том что происходит в приложении практически невозможно.
Я в этом плане солидарен с Дэном Абрамовым который сказал, "- Если вы точно не знаете зачем вам Redux, скорее всего он вам не нужен. Не используйте Redux пока у вас нет проблем в React".
А вообще я бы пошел еще дальше и сказал "- Не используйте вообще ни чего до тех пор пока вы реально не столкнулись с проблемой которую это что-то решает". Не говоря уже о том что всегда и всем своим коллегам говорю: "- Ребята, давайте изучать фундамент, мы же профессионалы, а не религиозные фанатики".
PS И кстати о Redux приложениях, я до сих пор не смог найти примера более менее крупного приложения на нем содержащего хотя бы пару тройку ограниченных контекстов, что-нибудь типа CRM, CMS или ERP. Что-нибудь сложнее todo или типичного проекта из пары десятков файлов. Буду крайне признателен если кто-нибудь сбросит хотя бы парочку ссылок на среднего размера проекты на React/Redux.
Я так понимаю это обо мне? Просто я идеально подхожу — 25 лет, опыт программирования лет 7 всего, все дела.
Ну во-первых, почему люди, не владеющие и не набившие кучу шишок в реакте учат сообщество как этим реактор пользоваться? Разумеется в ответ слышат критику, а потом начинают «да во фронтэнд понабрали хипстоты». (Это е относится к автору, критику он вроде слушает, а не спускается до авторитета возврата)
Во-вторых, интерфейсы были сложнее, кто же спорит. Но сменился инструментарий, сроки, количество людей под проект и требования. Об этом почему-то забывают.
В-третьих не знаю как там в 80х, в 2018 принято по MVC считать семейство паттернов, включающих MVP, MVVM и ещё с десяток других (в статье, к слову, описан MVP). И несмотря на то, что это даже на хабре обсосано со всех сторон, некомпетентными являются молодые. Потому что не путаются в современной терминологии, я полагаю.
Ну и в конце концов как-то вы бодро решили охарактеризовать компетентность фронтов. Да, признаю, не смог с нуля спроектировать архитектуру компьютера, подсмотрел ARM и AVR, признаю — не смогу развести на плате гигагерцовую шину, живу в вымышленном мире, где многопоточность ни разу не означает параллельность и где параллельные вычисления могут быть и на одном потоке, и вообще я же фронт, мне все ваши суперскалярности, векторные вычисления, зеленые потоки незачем. Да и в алгоритмах я ноль, хоть и пришлось однажды объяснять стереотипно всезнающим бэкэндам, что при потоковых данных на вход лучше использовать сортировку слияниями при потоковых данных, но они все равно не слушают и используют готовые решения, потому что я фронт, куда нам хипстерам там.
Это я к тому, что… что за предвзятость на хабре? Это, как минимум, не профессионально. Тою что у вас есть неудачный опыт с редуксом, ещё не говорит о том что редукс плох. Он тоже появился не на пустом месте, а после опыта того же бэкбона, ангулара, кнокаута. И он появился потому что на фронте нет хранилища, откуда можно читать синхронно, и пусть весь остальной мир подождёт. И нет потоков, ничего нет. Теперь есть, но да, мы ведёмся на маркетинг, потому что не канон, не как у дедов.
И вообще, какого черта я защищаю редукс? Мне он самому не нравится, мне в нем привлекает только идея единого хранилища, а остальное переусложенно на пустом месте.
Я так понимаю это обо мне? Просто я идеально подхожу — 25 лет, опыт программирования лет 7 всего, все дела.
Это об отрасли, ни каких личностей у меня нет ни каких личных претензий ни к вам ни к кому то еще. Это не высосанная из пальца проблема в отрасли не хватает опытных разработчиков. Во фронтэнде эта проблема особенно острая, потому что каждый второй разработчик вышел из дизайнера или верстальщика.
Ну во-первых, почему люди, не владеющие и не набившие кучу шишок в реакте учат сообщество как этим реактор пользоваться
Потому что React это всего лишь библиотека, потому что есть люди которые знают фундамент и понимают принципы работы всего того что лежит под капотом React, а так же под капотом всех внешних слоев, им не нужно набивать шишки тыкаясь в StackOwerflow и в документацию чтобы понять проблемы и суть вещей, они эти проблемы решали до появления React и прочих библиотек и фреймворков. Часть из этих людей набили кучу шишок еще тогда когда Фейсбука не было в проекте, другая часть посвятила всю свою жизнь (десятки лет) каким то фундаментальным проблемам в области разработки в той или иной отрасли. И для меня лично странно почему многие в упор отказываются перенимать этот опыт, вместо этого поддаваясь религии и мнению большинства (сообщества). JS сообщество переживает тот же кризис что в середине 2000 переживало PHP сообщество (Там тоже поначалу все изобретали сами не учась у предыдущих поколений. Хорошо что сейчас уже тенденция пошла в правильное русло, и есть реально грамотные разработчики и качественные продукты Enterprise уровня). Не переживайте так. Такое случается с любым не окрепшим организмом.
Это я к тому, что… что за предвзятость на хабре? Это, как минимум, не профессионально.
Нет у меня предвзятости, я просто озвучил кризис который есть в отрасли в целом и фронтэнде в частности, если вы выйдете за пределы Хабра и обратитесь ко всемирной сети и авторитетным людям в сообществе вы это непременно увидите, эти мысли высказывают ваши же коллеги вашего же сообщества.
Тою что у вас есть неудачный опыт с редуксом, ещё не говорит о том что редукс плох. Он тоже появился не на пустом месте, а после опыта того же бэкбона, ангулара, кнокаута
Я не сказал что он плох, я лишь сказал что мой опыт показал, что он не решил тех проблем которые мои знакомые фронтэндщики пытались с помощью него решить, он сделал только хуже. Причем не я принимал решение, я доверился этим людям, это их область и их сообщество. В итоге получил то что получил.
Redux же появился потому что Дэн Абрамов, который к слову не относится к этому самому большинству которое я обозначил и имеет достаточно стабильные и качественные фундаментальные знания, решал конкретные проблемы которые у него возникали. Более того, он их так или иначе решил, поспешив поделится своим решением с не окрепшим сообществом, которое завернуло все это хрен пойми куда. Последите за его публикациями в соц сетях и вы увидите сами всю его боль (Я на самом деле поражен его выдержке и манере объяснения того почему тот или иной разработчик неверно понял его концепции и неправильно их использует, я бы спился уже). Посмотрите как менялась документация по Redux и опять таки увидите всю его боль и увеличение кол-ва фраз «Не используйте Redux» на строку документации.
Перечитайте мой предыдущий комментарий, мой главный тезис в предпоследнем абзаце.
Кто не помнит своего прошлого, обречен пережить его вновь.
— Джордж Сантаяна
Я наблюдаю именно то что вы описали. А вы говорите то же что и Дядька Боб.
Становится грустно от того, что существует огромное количество полезной информации, но большинство просто игнорируют и слепо следуют трендам.
Вот что точно не профессионально — игнорировать опыт старших коллег(по опыту) и самонадеянно пилить свои велосипеды
Становится грустно от того, что существует огромное количество полезной информации, но большинство просто игнорируют и слепо следуют трендам.
Сложные абстракции тяжело даются, бородатые дядьки пишут материал через призму своих бород, соответственно их сложно понимать сейчас. Говорю как молодой специалист (год в профессии, PHP), но изучающий Computer Science
Тренд на то и тренд — деньги, популярность и престижность двигают людей, которые в основе своей живые организмы, которым нужно есть еду и потреблять дофамин. Так что тут все очевидно. Накидываем сюда лень, состояние образования, рынок — получаем то, что имеем :)
Я воспринял на свой счёт потому что пока только я тут критикую статью (прошу заметить, пытаюсь оставаться в конструктивном русле и вообще поддерживаю автора, просто кому-то (мне) стоит внимательнее следить за речью, а так же хочу сказать что автор переизобретает mobx. Там правда с другой стороны подходили, но результат тот же).
Ну а в остальном я в чем-то с вами согласен, в чем-то нет. Фронтов на рынке хороших найти сложно, это просто объективный факт. Но вот про реакт и шишки — не согласен. Дед Вася, который 50 лет ремонтировал жигули и знает все как работает под копотом вряд ли так снаскоку разберётся в современном автомобиле. Это я к тому, что реакт довольно специфично работает под капотом и вряд ли virtual dom — старое и хорошо забытое. Что-то похожее — да, но общих идей как это могло быть устроено, не всегда достаточно.
Ну а про молодость и кризис фронта — я не знаю что сказать. Повзрослеет и станет интерпрайзом, а сейчас молодая инфраструктура? Джава просто синоним слова интерпрайз, только что-то сейчас просто в безумном количестве тянет чужих фишечек, а гредл с мавеном далеки от идеала. И развивается она довольно активно, вон даже Котлины всякие выходит. Все нормально с js, наконец сложилась определенная инфраструктура, куча инструментов и возможностей. Вот то что на собеседовании заявляют «кому нужны эти прототипы, в es6 class написал и все» — это да. С тем что встречаются (не буду говорить про большинство, но очень много) люди, у которых ноль идей о том как это работает под капотом — я не буду спорить, даже наоборот поддержу. Но ни реакт, ни жс тут ни при чем. И думаю, это просто фронты почему то стали просто стереотипом малой компьютерной грамотности, хотя на собеседованиях на ту же джаву не все могут сказать чем отличается интерфейс от абстрактного класса.
Ну а почему тащат редукс и реакт везде? На это я знаю ответ и отвечу просто. Вакансии посмотрите — в 9 из 10 стоят эти два слова. Конечно же начинающие смотрят на то, что востребовано и пытаются учить, таща его куда только можно. Плохо это? Если бы они при этом учили что как и почему — нет. Но не учат. Плохо то, что эм мои сверстники не могут сказать где хранится переменная: в стеке или в куче (если вообще знают что это и зачем это), на какой черт нужен callee в js, или почему в usb можно подключить только 127 устройств (включая хабы)? Наверное да, но с другой стороны они что-то программируют и даже многие зарабатывают больше чем те, кто программирует уже лет 30.
Мне вот например статья зашла. Пример пусть и простой, но похож на то, что я хочу сделать для своих проектов на реакте и когда-то давно делал для AS3 во флэше, и на джаве. А вот почему это мне нравится — это уже другой вопрос.
Я искренне уверен, что код я пишу для людей, а не для интерпретатора. Так что, я хочу чтобы он был прост и понятен любому новоприбывшему на проект. С редаксом так не выходит. А вот PureMVC — работает на ура. При том что оно существует ОЧЕНЬ давно. Реакта тогда и в помине небыло. И этот фреймворк решает проблему на всех платформах до сих пор. Это развитая идея того самого MVC, упомянутого автором статьи. Эта модель гораздо ближе человеку и легче понимается, чем эти все рекурсивные вызовы и потоки.
Другим ключевым моментом, для меня, является тестопригодность приложения.
PureMVC созданный черти знает когда, хорошо ложиться в описаную Р. Мартином «Чистую Архитектуру». По сути просто имплементирует DiP.
А это значит, что мне не надо тащить в проект дополнительно 3-5 сложных фреймворка, чтобы протестировать все. Достаточно будет простого xunit фреймворка.
И я рад спросить совета у Деда Васи, который не только жигули чинил, а чинит сейчас новейшие Теслы. Ведь он не замкнут на реакте их 80х, а пробует и берется за все стоящее сегодня, с оглядкой на прошлое.
Не надо принижать достоинство и авторитет старшего поколения. Те люди, у которых надо учиться — успешно выполняют вашу работу и сейчас. А опыта у них на лет на 20 больше.
Редукс тащат всюду по тому, что об этом только хабр и поет. В том смысле, что актуальные новости на IT ресурсах — они именно про то что сейчас актуально. А проблема MVC была актуальна 20-30 лет назад. Новички смотрят главную страницу хаба, а там про MVC ни слова. Большая часть этих молодых людей в тематических вузах не училась, фундаментальных знаний не получала. Хорошие книжки слишком толстые, люди боятся их. А в тонких — ничего нет.
Старшим не верят.
Про «фронты» можно долго беседовать, но это из разряда холиваров. Я не верю что можно переубедить кого-либо в этой битве написав коммент. А стереотипы не на пустом месте рождаются.
Ещё года три назад каждая третья статья на реакте была про MVC, а теперь вдруг в нем все проблемы решились? Нет, просто большинство переключилось на другие вещи. Да и вообще — прогресс есть прогресс, то что мы видим и обсуждаем сейчас не более чем развитие того что было. Или вот MVC — канон и идеал? Притом что я не отрицаю, а наоборот утверждаю что вообще весь фронт — это только V в целом приложении и отдельно жить не может.
Далее, если вы будете писать pureMVC на фронте без использования библиотек — рано или поздно по мере усложнения приложения напишите свой велосипед, только наткнётесь ещё на специфичные для фронта проблемы, которых не было у других. Следует сразу взять например бэкбон. Замечательная, простая и понятная библиотека, всем рекомендую. Компонентный подход там кривоват, но все же. А потом дом медленный, да и не на пустом месте появились реакты и ангулары. Это не более чем развитие технологий и ничего в них плохого нет.
Читаемость — вопрос привычки. Для меня редукс вполне читаемый, хоть я его и не использую, как и большинство современных тенденций на фронте. Может конечно это связано с тем что я человек любознательный и для меня и Verilog вполне читаемый с типичными для него десятикратным вложением тернарного оператора, но скорее росто потому что я на это часто смотрю и для меня этотпричное окружение. Есть отличный ролик о том, как люди приходят с джавы на питон и продолжают писать в джава стиле. Это не совсем верный подход, поэтому какие-то конструкции, привычные для фронта, могут казаться непривычными для бэка. А ещё не стоит забывать, что редукс пытается походить на функциональный стиль, а в шапке и ветке обсуждений речь про ООП. И да, функциональщина действительно кажется дикой первое время (мне лично и не первое время тоже).
Ну а про тесты — так это ж хорошо. Есть большой выбор и свобода конфигурации. Нет здорового монолита, который все делает сразу, есть небольшие утилитки, которые отвечают ровно за то, за что они отвечают. И позади них стоят зачастую далеко не гавнокодеры.
Ну и я не принижал достоинства старых поколений и наоборот восхищаюсь элегантностью кучи решений. Того же упомянутого WPF, как по мне он в разы уместнее того ада, который наворотили в мире html/css/js последнее время.
В общем мой посыл в следующем: то что мы видим зачастую на фронте — это не велосипеды и переизобретение уже решённых проблем, а банальное развитие, ведь позади большинства из этих технологий стоят люди, в эрудиции которых сомневаться не приходится. Да даже банально глядя на историю развития видно, что начинали с тех же самых подходов, применяемых в других областях и прогрессировали. И ничего в них плохого нет, опять же просто физически сейчас на том же реакте и современном веб-стеке можно в одиночку и за пару месяцев максимум можно разработать то, на что раньше уходили годы у целых команд. А вот то, что люди просто не до конца их осваивают или берут подходящие под задачи инструменты и огребают неожиданные проблемы — это да, запросто.
Представьте, что у вас на странице сложная группа форм, которая требует порядка двух-трех сотен actions, из которых большая часть — асинхронные (и, значит, могут спавнить другие actions). Все еще будет легкочитаемо, без приложения в виде графа состояний?
Почти верно. Экшн — это все, что отправляется диспетчеру, ну то есть все, что можно засунуть в ф-ю dispatch. В том числе — все то, что может быть обработано подключенными middlewares. Асинхронные экшоны, например. Вообще, без экшонов, которые спавнят экшоны (в том или ином виде), вы никакую нетривиальную логику реализовать просто не сможете.
virtual dom — это обычный буфер. Понятию буфера (а так же принципам работы с буферами того или иного вида того или иного назначения) не один (и не два и даже не три) десятка лет. Более того — операции с домом буферизовывались задолго (очень задолго) до того как фейсбук вообще появился.
Это я к тому, что… что за предвзятость на хабре? Это, как минимум, не профессионально
На мой взгляд вот это тоже не профессионально, хотя повторюсь у меня нет к вам претензий.
неграмотное и противоречащее использование всего реакта, получившего свое название от слова «реактивность»
Сильное заявление, проверять я его конечно же не буду.
Стандартный вопрос на собеседовании: почему так делать не стоит и как это писать правильно.
Наверное большинство даже Таненбаума не читали. Если смотреть на это все как учит он, то все сразу становиться на свои места. Получается решение на одних вариантах компьютеров, потом компьютеры становятся мощнее, но появляются другие по-меньше и слабее, и все эволюция повторяется.
Вот по сути конкретного вопроса (фронт-энда). В вебе был тонкий клиент потому, что устройства были слабые и возможностей делать что-то больше, чем добавить пару элементов или чем-то пошевелить не было. Потом появились устройства мощнее, но то, как это все строить правильно уже давно решено в Java Swing/AWT, WPF, GTK, Qt и прочих. Но как правильно подмечено, большинство современных программистов и особенно на фронте даже не пытаются получить нормальное образование.
P.S. На днях опросил 4 человек и 2 из них не знали что такое кэш-промахи, 3 сеньор и один выше (он тоже знал).
Этот проект как раз ответ бесконечным ToDoApp которые так красиво делать на vanilla backbone.
(«А теперь перейдем к чему-то более практичному. Предположим Ваш марсолет должен ....»)
Даже live пример посмотрел, мы в начале 2000-ных еще до выхода jQuery писали проекты в разы сложнее чем этот «real world example».
Мне сложно представить какой тестовый проект можно создать
CRM, или ERP или даже более менее сложная CMS. И я не говорю про тестовый, может где то есть OpenSource CRM/CMS построенная на React + Redux как продукт. Хотелось бы взглянуть на такой проект, как там все устроено и на сколько Redux помог.
Кстати что такое разделение приложения на контексты
martinfowler.com/bliki/BoundedContext.html
Ой, ну что вы! Куда там тем дряхлым «сложным» интерфейсам до сверхзадачи вида отрисовки одного счетчика в двух местах :)
> А вообще я бы пошел еще дальше и сказал "- Не используйте вообще ни чего до тех пор пока вы реально не столкнулись с проблемой которую это что-то решает".
Но тогда ведь придется разбираться, зачем инструмент нужен (какую проблему решает), вместо того чтобы просто следовать хайпу :(
Как итог, изменение требований к более или менее средней форме или куску интерфейса занимает несколько недель и то и месяц разработки (в попытках понять, а как же это сделать на Redux + React + Saga + Thunk + Redux Form +… «правильно» и чтобы работало. Разработка превращается в забивание костылей, потому что «так не работает», вот так «нарушен источник правды», ну а так «в реакте не принятно»),
Не знаю, совпадение или нет, но всегда, когда слышу о проблемах с реактом, оказывается, что там ещё есть редакс, а основные проблемы с редакс-формами. Вокруг них основные костыли забиваются.
Вот насчет redux-form полностью соглашусь. Имею один react проект и главная проблема (самый запутанный код) там именно в redux-form. К сожалению каких-то альтернатив (кроме как реализовать форму полностью самому) не вижу. И да, в маленьких, простых формах redux-form работает прекрасно.
formik
смотрел. Судя по тому что увидел в README это тот же redux-form только без redux'а. API очень похоже. final-form
(после очень бегло взгляда) вроде как тоже. Ну т.е. проблемы будут те же самые что и при использовании redux-form. Как минимум в моем случае.
Тут буквально вчера наткнулся на form-for
. Вот это более интересный вариант с другим подходом. Наверное в следующем react проекте буду его пробовать.
Ну т.е. проблемы будут те же самые что и при использовании redux-form
А можно кратко — какие именно проблемы вам попались, что больше всего не понравилось, как и обошли их? Я redux-form
не использовал в деле, только документацию глазами пробежал. Интересен боевой взгляд на этот вопрос.
Для относительно простых форм которые повторяют модель redux-form только помогает и упрощает.
Основная проблема состоит в том что начальные значения нужно передавать при создании формы и они передаются самой форме и должны повторять структуру формы. У нас было так что в стэйте (и в базе) данные хранятся в одной структуре, а форме они должны отображаться в другой структуре. Соответственно при инциализации, при сабмите форме, при отображении ошибок, нужно данные из одной структуры перегонять в другую. Если бы я мог указать начально значение непосредственно для поля, то такой проблемы не было бы, я просто извлекал из стейта значение для поля. То же самое с отображением ошибок.
В другом проекте, я столкнулся с тем что redux-form заточен под работу с Promise при сабмите. А там особенность была в том что promise в action нельзя было передать. Пытался сделать полностью через action'ы, но в этом случае некоторые моменты работали не корректно.
Основная проблема состоит в том что начальные значения нужно передавать при создании формы и они передаются самой форме и должны повторять структуру формы.
Если данные пришедшие с сервера повторяют структуру формы, то у архитектора данного решения каша в голове и полностью отсутсвует понимание «инкапсуляции» и разделения системы на слои и ограниченные контексты.
Сразу говорю, оставляю за собой право не отвечать на сообщения, ибо здесь такое ощущение, что собрались какие-то ненавистники редакса, которых переубеждать желания никакого нету.
Напишу лишь пример реального проекта, вы просили его. Я сейчас являюсь одним из фронтенд разрабов проекта https://rawg.io
Никаких указанных вами проблем в разработке не возникает, редакс на мой личный взгляд — прекрасная вещь, очень сильно упрощающая разработку. Страшно вспоминать свои дни с бекбоном и отдельными стейтами компонентов. Повторяю: спорить не собираюсь, время ценнее.
Эм нет. Ну т.е. Да, но нет. Короче — это редуксовский антипаттерн, а не то как его нужно готовить. Ну например когда вы захотите это тестировать — вам придётся каким то образом это инжектить. Да и вообще зачем вообще тогда редукс нужен?
На мой взгляд mobx тут на порядок удобнее при таком же применении. Основа редукса — отсутствие сайд эффектов, которая там про тестирование. Отсюда весь геморрой с экшенами. Если уж забивать на три столба редукса — так по полной. Гулять так гулять, как говорится. Хотя в целом идея мне нравится, но вот правда интеренсо как тестировать без инжектов, поделитесь?
C MobX вам никто не запрещает хранить все данные в одном сторе.
Как я понимаю, даже если написать подобное, то никаких преимуществ MobX не обнаружится. У него идеология другая — нет какого-то абстрактного хранилища, в которое можно положить какой-то абстрактный стейт и манипулировать им. Есть вполне обычные объекты, инстансы обычных классов (использовал только с ES6 классами, можно и с ES5 объектами, но сам не пробовал) со своим стейтом, управлямым методами класса (сеттерами, мутаторами), но клиент класса вместо обычного получения среза состояния может подписаться на доставку этого среза, начальную и последующих изменений. MobX запомнит граф объектов, который участвовал в получении среза и при их изменении доставит новый срез.
Сравнивать подобным образом можно на каких-то простых приложениях
Вью отображает какой-то срез стейта независимо от того где и как хранится стейт. Стейт, хоть с редакс, хоть с мобкс представлен графом объектов. Просто в редакс эти объекты вырожденные, только состояние, да ещё принято ссылки между объектами реализовывать с помощью ключей и/или денормализовывать граф. А в мобкс полноценные объекты с поведением и связи выражаются обычными JS-ссылками.
Граф модели нормализован с ссылочными связями, в редакс его принято или денормализовывать, или делать связи через значения ключей.
Типичная редакс-модель, вернее та её часть, которая отвечает за хранение данных — стор, — не является чистым объектным графом. Кроме объектной связей нод друг с другом есть связи по идентификаторам, о наличии которых объект стора не знает, это отвественность селекторов и редюсьеров знать о них и поддерживать ссылочную целостность. Например, типичным для редакса графом является:
{
users: [
'123': {
id: '123',
login: 'VolCh'
},
'456': {
id: '123',
login: 'babylon'
}
],
currentUserId: '123'
}
этот json (пришедший с сервера например) является прямым представлением модели редакс. Чтобы показать текущего юзера нам нужно делать что-то вроде <User user=
store.users[store.currentUserId] />` и следить как за обновлением store.currentUserId так и за store.users.
В MobX же генерирующий модель код из этого json выглядит примерно так:
store.users = data.users.map(userData => new User(userData));
store.currentUser = store.users[data.currentUser];
то есть вместо символической ссылки о семантике которой должны знать клиенты стора, используется обычная js ссылка, store.currentUser === store.users['123']
. Следить нужно только за изменениями store.currentUser чтобы показывать актуальные данные текущего пользователя. Чистый нормализованный js-граф.
Тут не в текстовом представлении дело, json мог быть денормализованным в примере, а в способе нормализации самого хранилища. Хранилище Redux не поддерживает нормализацию, оставляя её полностью на откуп разработчика и, как минимум, не рекомендуя использовать для неё нативные объектные ссылки вне пределов одного дерева. А сами ноды хранилища — вырожденные объекты без поведения. А в MobX поддерживается и рекомендуются нормализация через объектные ссылки. Хранилище MobX — самодостаточный нормализованный граф полноценных объектов.
Полноценные объекты — объекты с собственным поведением, с методами, с функциями. Если у вас require("./lib/methods") возвращает функцию, то такого не должно быть в хранилище редакс и такое не сериализуется в json. Вернее можно попытаться сериалзировать исходники для последующего eval например, но это точно не редакс-вэй.
Например, "Объект в программировании — некоторая сущность в компьютерном пространстве, обладающая определённым состоянием и поведением, имеющая заданные значения свойств (атрибутов) и операций над ними (методов)" Нет методов или нет свойств — это уже не полноценный объект.
Для меня объект — это прежде всего основное понятие ООП, независимо от языка. Хэш-таблица там под капотом или вычисленные на этапе компиляции целочисленные смещения — дело десятое, пока не встаёт вопрос производительности. Основной интерфейс объекта — его методы, даже если свойства де-факто не сокрыты. Сериализация/десериализация (вернее нормализация и денормализация стейта) — ответственность самого объекта в общем случае.
Хотелось бы посмотреть пример такого же кода, как в моем репозитории, только на Mobx. Максимально просто и с одним стором. Буду рад если предоставите
На MobX это выглядело бы как-то так (писал по памяти, не проверял, но суть приблизительно такая):
////////////////// MODEL //////////////////
class User {
@observable token: AuthToken = null;
@observable error: Error = null;
@observable loading = false;
@action.bound
signIn (options: AuthOptions) {
if (this.loading) return;
this.loading = true;
try {
this.token = await api.signIn(options);
} catch (error) (
this.error = error;
)
this.loading = false;
}
}
////////////////// VIEW //////////////////
@inject(store => ({ user: store.user }))
@observer
class SignIn extends React.Component<{ user: User }> {
render() {
const { user } = this.props;
if (user.loading) {
return <div className="loading" />;
}
if (user.error) {
return <div className="error">user.error</div>;
}
if (user.token) {
return <div className="logged_in">Logged in</div>
}
return <LoginForm loginAction={user.signIn} />
}
}
На $mol_mem ещё проще:
model:
class $my_auth {
@ $mol_mem
options( next : $my_auth_options = null ) { return next }
@ $mol_mem
token() {
return this.options() && $my_api.sign_in( this.options() )
}
}
view composition:
$my_app $mol_view
Auth $my_auth
options?val <= auth_options -
sub /
<= Login $my_app_login
data?val => auth_options
<= Content $mol_view
sub / \Logged in
view logic:
export $my_app extends $.$my_app {
sub() {
return this.Auth().token()
? this.Content()
: this.Login()
}
}
Ну не знаю, для меня простым это совершенно не выглядит, особенно в view composition, Perl какой-то. Да и options( next )
выглядит как магия тому, кто не знаком с синтаксисом.
А где loading и error?
В том и суть, что они обрабатываются автоматически и не требуют ручной работы. Можете поиграться с живым примером: http://mol.js.org/app/demo/#demo=mol_app_files_demo
А тут человек делает аналог для вашего любимого реакта, причём даже с кнопкой "retry" в случае ошибок: http://zerkalica.github.io/rdi-examples/?page=todoapp
TheShock любой незнакомый синтаксис выглядит как магия. Тем, кто в нём разобрался он прост, понятен и гораздо более удобен, чем нагромождение костылей, чтобы быть похожим на html.
babylon я не понял вопроса. Тут просто идёт наследование с переопределением метода sub
, чтобы он возвращал не обе страницы, а только одну из них. И да, я накосячил — возвращать, конечно же, надо массив.
он прост, понятен и гораздо более удобен
Я понимаю, что вы любите свое творение и оно вам кажется самым талантливым и умным, но ваш «view composition» выглядит просто безвкусным нагромождением символов, уж простите, а значит не вызывает никакого желания в нем разбираться
выглядит просто безвкусным
И без tree части mol дают автоматизацию многих вещей: наследование стилей, обработка loading/error по-умолчанию, упрощение работы с асинхронностью — код, выглядит как синхронный, без async/await и промисов, с возможностью retry в случае ошибки, типобезопасные контексты.
А если нужно кастомный loading и error?
function loader(child, loading) {
try {
return child()
} catch(error) {
if (error instanceof Error) throw error
return loading
}
}
const ImgView = connect(({store}) => {
return <p>
{loader(
() => <img src={store.game.url} />,
<div>Custom loading...</div>
)}
</p>
})
fiddle с кастомной обработкой
Для сравнения fiddle с общей обработкой
Главное тут обработка статусов загрузки и ошибок как исключений. В альфе react 16.4 suspense api сделали похоже.
class $my_auth {
@ $mol_mem
options( next : $my_auth_options = null ) { return next }
@ $mol_mem
token() {
return this.options() && $my_api.sign_in( this.options() )
}
}
////////////////// MODEL //////////////////
class User {
@mem token: AuthToken = null;
@mem options: AuthOptions
@mem get token(): string {
throw api.signIn(this.options)
}
@action signIn(options: AuthOptions) {
this.options = options
}
}
////////////////// VIEW //////////////////
@observer class SignIn extends React.Component<{ user: User }> {
render() {
const { user } = this.props;
try {
if (user.token) {
return <div className="logged_in">Logged in</div>
}
} catch (error) {
if (error instanceof Promise) return <div>Custom loading...</div>
return <div>Custom error: {error.stack}</div>
}
return <LoginForm loginAction={user.signIn} />
}
}
Над try/catch можно наворотить хелперы, вроде jsx-оберток, думаю, принцип понятен.
Был бы более предметный разговор, если б сделали полноценный пример в fiddle.
class Loader extends React.Component {
state = {}
componentDidCatch(error) {
this.setState({error})
}
render() {
const {error, onLoading, onError, children} = this.props
if (error intanceof Promise) return onLoading
if (error intanceof Error) return onError
return children()
}
}
class SignIn extends React.Component {
render() {
const { user } = this.props;
return <Loader onLoading="Custom onLoading="..." onError="Error">{() =>
user.token
? <div className="logged_in">Logged in</div>
: <LoginForm loginAction={user.signIn} />
}</Loader>
}
}
Был доклад про suspense api, где в примерах все очень похоже.
Например, в js нет нормального паттерн матчинга, но можно эмулировать его на мапе.
class Loader extends React.Component {
state = {}
componentDidCatch(error) {
this.setState({error})
}
render() {
const {error, match, children} = this.props
if (!error) return <children/>
const handler = match.find(rec => error instanceof rec[0])
if (handler) return handler[1]
throw error
}
}
class SignIn extends React.Component {
render() {
const { user } = this.props;
return <Loader match={[
[MyError1, 'My error 1'],
[MyError2, 'My error 2'],
[Promise, 'my loading'],
]}>{() =>
user.token
? <div className="logged_in">Logged in</div>
: <LoginForm loginAction={user.signIn} />
}</Loader>
}
}
Ну как же, вариант с mobx вполне себе универсальный, без продвигаемого вами вырвиглазия с переносом логики отлова эксцепшенов и isloading в разметку. SRP кровавыми слезами плачет.
вариант с mobx вполне себе универсальный,Нет, не универсальный, более того, он нарушает инкапсуляцию и приводит к сильному зацеплению компонент.
Что б показать лоадер, надо обратиться к деталям User. Если SignIn упрятан на 10 уровней на странице, а лоадер надо нарисовать на главной. То этот User.loading придется запросить на главной.
Если в дочерних компонентах много загружаемых данных, а лоадер надо показать один, то сторы придется пробрасывать на вышележащую страницу и там комбинировать из множества loading/error один.
Вариант с mobx требует обязательной кастомной обработки статусов в каждом конкретном случае загрузки данных, если этого не сделать, никакой ошибки или лоадера пользователь не увидит. Когда много загружаемых данных — пропустить это легко.
Данные User могут попадать в компонент не напрямую, а через computable — тогда придется либо пробрасывать error/loading, либо сам User этому компоненту ради отлова статусов.
продвигаемого вами вырвиглазияА вы покажите аналогичный пример на mobx c теми условиями, которые вы напридумывали. С паттерн-матчингом, передачей обработки другому лоадеру по условию и т.д. Тогда вместе и посмеемся.
с переносом логики отлова эксцепшенов и isloading в разметку. SRP кровавыми слезами плачетВполне себе SRP, это связано с задачей ux, нарисовать лоадер вот тут, эти ошибки показывать тут, а эти тут.
Реакт в целом, это не чистое SRP. Контексты и провайдеры в jsx, state в компонентах, логика в хуках никому не мешает пока, идут на компромисс. В некоторых случаях управляюший jsx читается лучше, чем if-ы с try/catch ами разбрасывать по шаблонам.
Абсолютно универсальный. Вы не сможете придумать ни одного случая, когда бы он не сработал.
> более того, он нарушает инкапсуляцию и приводит к сильному зацеплению компонент.
Как раз все наоборот. Логика загрузки данных и обработки ошибок — в одном месте, логика рендера — в другом. И они никак не связаны. В рендере вообще даже нет никакой информации о том, что кто-то что-то загружает, что могут быть какие-то ошибки и т.п. — туда на вход подаются просто данные и все. Другое дело у вас — внутри рендера мы знаем о загрузке, о том что там может быть поймана ошибка и т.д.
Компонент на мобх будет работать даже в том случае, если вообще никакой загрузки данных нет. Ваш — не будет.
> Что б показать лоадер, надо обратиться к деталям User.
В том и прелесть, что (в отличии от вашего варианта) не надо! Компонент не знает ни о каких деталях user.
> А вы покажите аналогичный пример на mobx c теми условиями, которые вы напридумывали.
Так выше этот пример привели. Вы все не можете его повторить в нормальном виде.
> Вполне себе SRP, это связано с задачей ux, нарисовать лоадер вот тут, эти ошибки показывать тут, а эти тут.
Ваш рендер знает о загрузке данных, о запросе к апи, о наличии ошибках, занимается их перехватом и обработкой. Он знает о том, что signin делает запрос к апи. А не должен. Рендер в примере мобх этого всего не знает.
> В некоторых случаях управляюший jsx читается лучше, чем if-ы с try/catch ами разбрасывать по шаблонам.
В шаблонах не должно быть никаких трайкотчей. В этом суть. У вас они есть. А не должно быть.
Абсолютно универсальный. Вы не сможете придумать ни одного случая, когда бы он не сработал.Оба способа можно заставить работать в любом случае. Важно количество копипаста, сцепленность компонент, возможность кастомизации поведения по-умолчанию без рефакторинга. Здесь пример на catch будет работать также, а кода будет меньше. Не надо async/await try/catch в сервисе, достаточно в Loading обернуть то, на месте чего мы хотим показать крутилку или ошибку.
Как раз все наоборот. Логика загрузки данных и обработки ошибок — в одном месте, логика рендера — в другом. И они никак не связаны. В рендере вообще даже нет никакой информации о том, что кто-то что-то загружает, что могут быть какие-то ошибки и т.п. — туда на вход подаются просто данные и все.Рендер это функция, исключение там вполне может быть, также как и конструкции, связанные с их обработкой, обработка ошибок и статусов тесно связана с uix. То, как представлена эта обработка — в виде условий и переменных или в виде catch-подобных структур, зависит от задач, развития инструментов и требуемой масштабируемости. Никаких особых преимуществ у переменных с условиями тут нет.
Другое дело у вас — внутри рендера мы знаем о загрузке, о том что там может быть поймана ошибка и т.д.В случае примера с mobx тоже знает, в SignIn. Мы все-равно это знание где-то разместить должны, в виде if (user.loading) или еще как. В случае с ексепшенами механизм более гибкий, т.к. появляется возможность разместить это знание где-то выше, не вдаваясь в детали дочерних компонент (наличие User в каком-то SignIn).
Компонент на мобх будет работать даже в том случае, если вообще никакой загрузки данных нет. Ваш — не будет.С чего вы взяли, все прекрасно будет работать.
В том и прелесть, что (в отличии от вашего варианта) не надо! Компонент не знает ни о каких деталях user.Если вы про SignIn, то нет разницы, он также знает:
if (user.loading) {
return <div className="loading" />;
}
if (user.error) {
return <div className="error">user.error</div>;
}
Если про вышележащий — не знает и там и там.Так выше этот пример привели. Вы все не можете его повторить в нормальном виде.Я повторил самым первым примером, кода получилось меньше и имхо он вполне нормальный. Потом вы добавили доп. условий по кастомизации, что-то вроде: часть ошибок обработать в SignIn, а часть передать на обработку выше. На ексепшенах это просто сделать без лишних пропсов и рефакторинга.
Ваш рендер знает о загрузке данных, о запросе к апи, о наличии ошибках, занимается их перехватом и обработкой.Не надо передергивать. О запросах к апи знает модель, также как и в mobx. Рендер знает о том, что что-то в этом поддереве компонент может загружаться и надо нарисовать на его месте загрузку и обработать ошибки, если внизу их никто не обработал.
Он знает о том, что signin делает запрос к апи. А не должен. Рендер в примере мобх этого всего не знает.Да о чем вы вообще, компонент SignIn в mobx знает и error/loading и о экшене signIn. В моем примере как раз может не знать, это знание может быть выше, хоть в корневом компоненте. Действует принцип — все, что не обработано внизу — попадает наверх.
В шаблонах не должно быть никаких трайкотчей. В этом суть. У вас они есть. А не должно быть.Есть класс задач, которые удобнее решать исключениями или их имитацией. А должно/не должно, это выбор здравого смысла и архитектора. Разработчики реакта озаботились этой проблемой, если вы еще не посмотрели выступление Дэна про suspense api, советую посмотреть.
Ваш — нельзя, потому что он гвоздями к логике прибит. Поменял логику — отвалился.
> Не надо async/await try/catch в сервисе, достаточно в Loading обернуть то, на месте чего мы хотим показать крутилку или ошибку.
Как раз в сервисе try/catch/await и надо, чтобы этих try/catch/await не было в рендере, как у вас. Рендер должен рендерить, а не делать запросы к апи и обрабатывать их ошибки.
> Рендер это функция, исключение там вполне может быть
Безусловно. В процессе вывода результата может выпасть исключение. Но это будет исключение рендера, а не исключения к запросу апи. О том, что ваши запросы апи могут выбрасывать исключения, о том как их обрабатывать и о том, что вообще есть какие-то запросы к апи — рендер вообще знать не должен.
> В случае примера с mobx тоже знает, в SignIn.
А сигнин — этот метод модели. В рендере мы знаем только, что в модели есть такой метод, что его надо навесить на кнопку. Все. Никакого знания о том, что этот метод делает, как работает, какие исключения бросает и как их надо обрабатывать. Все это происходит внутри модели. Компонент даже не знает о том, что, собственно, сигнин меняет те самые передаваемые ему данные (у вас — знает).
> Если вы про SignIn, то нет разницы, он также знает:
Еще раз — сигнин это метод модели. Он не принимает участия в рендере. Все, что знает рендер — есть такой метод с таким именем.
> Да о чем вы вообще, компонент SignIn в mobx знает и error/loading и о экшене signIn. В
Нет, не знает. Он просто принимает данные и выводит их. Он не знает откуда эти данные взялись, как, какие ошибки могли быть выброшены при их обработке и как их следовало обрабатывать. Ничего этого он не знает.
> Действует принцип — все, что не обработано внизу — попадает наверх.
проблема в том, что вы _внутри рендера_ обрабатываете ошибки апи. Логике обработки ошибок апи внутри рендера делать нечего. Это наркомания.
> Есть класс задач, которые удобнее решать исключениями или их имитацией.
Вот они в мобх прекрасно и решаются при помощи исключений, тольк оисключения эти в модели, а не в рендере. Рендер о них ничего не знает. В итоге вы получаете прекрасный переиспользуемый компонент, который получает на вход данные и их отображает. В вашем случае компонент намертво гвоздями прибит к логике запроса к апи и обработке ошибок этого апи.
1) Логировать можно с помощью классического паттерна декоратор: https://en.wikipedia.org/wiki/Decorator_pattern
3) Изменять свойство другого класса можно. Методы будут храниться там, где вы посчитаете нужным в зависимости от композиции своих объектов.
4) Вот так, например
export class Application {
@observable user = new User();
}
А что не так с декораторами и console? :)
// log.ts
import { toJS } from "mobx";
export default function(target: any, methodName: string, descriptor: TypedPropertyDescriptor<any>) {
const originalMethod = descriptor.value
descriptor.value = function(...args: any[]) {
console.log(target.constructor.name);
console.log("| Previous State:", toJS(this));
console.log("| Method:", methodName);
console.log("| Arguments:", ...args);
const returnValue = originalMethod.apply(this, ...args);
console.log("| Next State:", toJS(this));
console.log("\n");
return returnValue;
}
return descriptor;
}
// Counter.ts
import { observable, action } from "mobx";
import log from "./log";
export default class Counter {
@observable count = 0;
@action.bound @log increase() { this.count += 1; }
@action.bound @log decrease() { this.count -= 1; }
}
// CounterView.tsx
@observer
export default class extends React.Component<{ counter: Counter }, {}> {
render() {
const counter = this.props.counter;
return (
<div>
<button onClick={counter.decrease}>{"-"}</button>
<span style={{ padding: "20px" }}>{counter.count}</span>
<button onClick={counter.increase}>{"+"}</button>
</div>
)
}
}
2) Отсутствие транзакций в вашем коде. То есть изменения token и loading вызовут render() два раза.
action — для создания транзакции. Но даже без этого, то, на скоьлко я знаю, в реакте можно изменять состояние много раз в рамках потока — он не будет перерендеривать до конца выполнения, а лишь помечает объекты как грязные.
3) Можно ли изменять свойства другого класса? Не только User. И где тогда хранить эти методы? В каком из классов?
Обычные подходы ООП. Берете необходимую модель и точно так же дергаете методы. Храните в тех местах, где они логично должны быть по DDD. Когда пишешь на МобХ — таких вопросов не возникает, оно все логично ложится. Нету ощущения из Редакса, что постоянно костылишь и создаешь свалку.
4) Хотелось бы видеть код связки класса User и стора.
Используем DiContainer, получаем простой код. Вот я специально навернул много разных связей:
class User {
@observable token: AuthToken = null;
@observable error: Error = null;
@observable loading = false;
@inject(Notifications)
private notifications: Notifications;
@inject(TokenContainer)
private tokenContainer: TokenContainer;
@inject(Statistics)
private statistics: Statistics;
@action.bound
signIn (options: AuthOptions) {
if (this.loading) return;
this.loading = true;
try {
this.token = await api.signIn(options);
this.tokenContainer.registerToken(this.token);
this.statistics.load();
} catch (error) (
this.error = error;
this.notifications.addError(error);
)
this.loading = false;
}
}
Но если сравнивать с redux-light — последний мне ближе, по перечисленным выше причинамЯ понимаю, что библиотека своя, любимая. Ребеночек самый умный, хороший и талантливый.
Костылить в редаксе не обязательно. Заменить классы из вашего примера модулями и будет то же самое.Ну да, рисуем сову: кружочки, потом всю остальную сову. А если надо добавить фичу: просто вставь if — и фича готова. Заменил классы на модули и вот МобХ вовсе не МобХ, а совсем даже Редакс. Единственное, не понимаю, как я до такой простой и гениальной идеи сам не дошел, плавала ведь на поверхности. Только о чем тогда вы со мной спорите? Что «модули» звучит лучше, чем «классы»?
1) Логировать удобно не получится. Например вот так:Ну вам уже написали несколько вариантов. Вот только ваш код на упрощенном редаксе все-равно в полтора раза больше на моем обычном МобХ. Можно вставить возле каждого изменения console.log и все-равно бойлерплейта на МобХ будет меньше.
2) В redux подписку можно использовать и для любой другой сквозной функциональности, например показать alert на SIGN_UP_SUCCESS. Без дополнительных усилий.Видите ли, в MobX тоже есть подписка на изменения. Но она так редко нужна, что я не понимаю, почему вы это считаете киллер-фичей.
3) У redux нативный, неизменяемый объектный граф. Очень просто сериализовать и загружать initialState.… что делается чуть чаще, чем никогда, потому что стейт содержится на сервере, подгружается по мере необходимости, иногда зависит от сторонних API, лучше хранить его рядом с кодом, который его обрабатывает (тем более типизацией мы не пользуемся, потому бегать по файлам — дополнительная головная боль), initialState — это всего-лишь 5% от всего кода клиентской модели, а ни один адекватный серверщик не согласится делать работу клиента просто потому что на редаксе так проще. Еще одна никому не нужная киллер-фича?
Ужас дебага Observable познал еще во времена использования ReactiveUI в .NET.А я понял какие подлые ёжики, когда ел овсянку.
3) Не представляю как все это дебажить.Видите ли, я прекрасно понимаю, что вы последнее время программируете на чем-то похожем на редакс, у вас сложный, неявный поток кода, который сложно дебажить, зависимости которого сложны и непонятны, вы стараетесь как-то с ними справиться спамя в консоль огромную гору логов и думаете, что мы с MobX мучаемся еще больше, потому что у нас нету возможности легко логировать. Этим вы мне напоминаете чукчу из анекдота. Но у нас олени не устают — мы просто не пользуемся оленями.
Подходит русский и дает совет:
— Купи КамаЗ в него больше вместится.
Послушал чучка и купил. Через год опять встречаются, а чукча на тележке снег везет.
Русский:
— Почему на машине не возишь?
Чукча:
— В КамаЗ больше входит, но олени быстро устают.
4) redux прост как топор (написал свою реализацию + свой react-redux менее чем за день) и ты сам все контролируешь. Mobx работает на великой и ужасной магии observable (одна реализация observableArray чего стоит).Сложный фреймворк или легкий — это проблемы исключительно создателя фреймворка. Я понимаю, что велосипедить и педалить тучу кода весело, но иногда я предпочитаю, чтобы за меня сделали всю неприятную работу, а самому в результате писать приятный код. В этом и задача фреймворка — инкапсулировать сложную и неприятную логику, чтобы код получается легким и приятным. А не писать легкий и приятный фреймворк для сложного и неприятного кода.
В итоге что имеем с mobx (если ошибаюсь поправьте):В итоге что имеем с redux-light (если ошибаюсь поправьте):
1. Прекрасный процедурный код (теперь процедурный не только dispatch, но код экшенов в виде getState/setState), наконец-то пропали редьюсеры, их все-равно в редаксе никто не любил
2. Возможность удобно логировать стейт, чтобы как-то компенсировать усложненный дебаг
3. Возможность загружать сериализированный initialState, что используется практически никогда
4. Кому-то другому, возможно, будет легче писать какой-то код, который мы видим пару раз в жизни из чистого любопытства
5. Код не напоминает о старой травме тем, кому не понравилось работать с ReactiveUI (не знаю, правда, напоминает ли mobx, вы им вообще пользовались или похожие слова увидели?)
6. ???
7. PROFIT!
Ну что, звучит как план, предлагаю на этой базе запилить статью, 200 звездочек на гитхабе и восторженные ликования — гарантированы
ps. Кстати, как покрыть тестами ваш `signIn`?
import { getState, setState } from '../store';
export async function signIn(options) {
if (getState().authentication.loading) return;
setState({
type: 'SIGN_IN_STARTED',
authentication: { loading: true }
});
let token = null;
try {
token = await api.signIn(options);
}
catch (error) {
setState({
type: 'SIGN_IN_ERROR',
authentication: {
loading: false,
error
}
});
return;
}
setState({
type: 'SIGN_IN_SUCCESS',
authentication: {
loading: false,
token
}
});
}
В подходе с MobX при необходимости можно замокать любую зависимость, подменить ее api фейковым, один раз послать ошибку, второй раз корректные данные и проверить, что будет в результате. Ну что-то вроде такого псевдокода:
test('success', () => {
var api = { signIn: FakeSendSuccess(new Token(42)) };
var user = new User();
user.api = api as Api;
user.signIn({ login: 123, password: 567 });
assert(user.loading, true);
assert(api.signIn.args[0], 123);
assert(api.signIn.args[1], 567);
api.signIn.next();
assert(user.error, null);
assert(user.loading, false);
assertNotNull(user.token);
assert(user.token.value, 42)
});
test('failure', () => {
var api = { signIn: FakeSendError(500) };
var user = new User();
user.api = api as Api;
user.signIn();
assert(user.loading, true);
api.signIn.next();
assert(user.token, null);
assert(user.loading, false);
assertNotNull(user.token);
assert(user.error.code, 500)
});
test('ignore-double', () => {
var api = { signIn: FakeCounter };
var user = new User();
user.api = api as Api;
user.signIn();
user.signIn();
user.signIn();
assert(api.signIn.callsCounter, 1);
});
Видите? Три простых и понятных шага полностью проверили валидность метода. И, главное, никаких сайд-эффектов! Функция test отработала и все, что происходило в Лас Вегасе — осталось в Лас Вегасе.
Так как тестировать ваш код? У вас стейт берется непонятно откуда. Потом функция пишет непонятно куда. У вас даже не так как в redux-thunk (передача Мира в качестве аргумента функции — звучит довольно грязно, но туда хотя бы можно передать свою функцию getState и свой dispatch). А что у вас? Да я бы лучше повесился, чем покрывал бы тестами ваш код. Там ни входные данные подменить, ни выходные проверить, ни вызов api замокать. Неудивительно, что вы всё-всё логаете, у вас там черт ногу сломит. Что-то откуда-то берется, что-то куда-то пишется при помощи чего-то, что вызывается.
появились какие то обиды и оскорбления
А можно, пожалуйста, пример оскорблений? Или критика вашего фреймворка — это уже личные оскорбления? Это называется проекция — то, что почувствовали вы сами — переносите на меня. Дополнительно это подтверждается попыткой убежать в последнем предложении.
Открою тайну — на реакте пишут и нативные приложения под мобилки, и не все из них тончайшие клиенты.
И что? Это не отменяет часть других аргументов. Вы ведь процитировали выборочно, так, чтобы было что ответить.
Более того, зависимости в ООП принятно передавать через конструктор и управлять ими через контейнер, а у вас как то по колхозному…
Принято и так, и так. Просто рекомендуется через конструктор. И что? Видите ли, я писал комментарии на хабре, а не документацию к своей библиотеки, более того не один раз указывал, что это псевдокод.
Опять ошибаетесь.
Окей, тут вы правы. Я невнимательно прочитал конец доки (уж извините, код не из приятных), так что инъекция в стиле redux-thunk у вас, похоже, поддерживается.
А многое вы просто пропустили мимо.
Скажу честно, лично мне ваш код нравится больше, чем оригинальный редакс. Но у вас совершенно не redux-light, он очень сильно не похож на редакс архитектурно, хотя похож внешне. В этом, кстати, проблема. Те, кто любит редакс — не захотят им пользоваться, потому что вы убрали важные вещи, а те, кто не любит — не захотят, потому-что слишком похож на redux и перенял слишком много его недостатков.
Редакс в принципе нужен для двух вещей:
1. доступ к единому, глобальному состоянию
2. доступ к единому, глобальному потоку событий
А как же единая точка изменения этого состояния?
Редьюсер действие описывает. А .dispatch() — это действие вызывает. Если у вас есть метод, который изменяет глобальное состояние, то точка изменения состояния — любой вызов данного метода.
Короче, «точка изменения» — это любой участок кода, при вызове которого состояние меняется.
Самая большая трабла с redux/react это асинхронная загрузка данных с сервера то есть то что присутсвует на каждой странице. У Вас фактически есть два возможности реализации
1) Пред-загрузить данные с сервера и после это отристовать компонент (сложновато)
2) Сделать отрисовать компонент-заглушку и после этого реальный компонент с данными (отять же трудоемко в каждом компоненте программировато два стстояния с данными и без данных) И в этом случае страница будет «прикольно» прыгать по мере загрузки данныхв компонент что мы модем наблюдать на сайтах даже на самых авторитетных типа NYTimes.
Новые возможности которые планируются в react должны порешать эту проблемы и как раз в основном на решение этой проблемы и направлены. Но redux в эту схему как-то не вписывается.
И в этом случае страница будет «прикольно» прыгать по мере загрузки данныхв компонент что мы модем наблюдать на сайтах даже на самых авторитетных типа NYTimes.
В случае с NYTimes будет уместно задать вопрос: "Зачем вам Single Page Application, если вы не сильно сложнее, чем Wikipedia и можете использовать Multi Page Application?"
Не там ответил
Хотел уточнить, в примерах, вы специально упростили, слив вместе контроллер и вью? Рассматриваете ли вы подход из PureMVC как более правильный? Где реакт компоненты будут играть роль именно UI компонентов и предоставлять апи, а над ними будет слой ViewController'ов?
Рад, что сообществу стать оказалась полезной и меня не сожгли на костре.
View и Controller слил воедино, потому что они всегда идут парой. Когда я осознал, что события JavaScript обрабатывают все мои кейсы, для которых в оригинальном MVC нужен был отдельный Controller, то решил убрать лишнюю сущность. Если вам нужно контролировать более сложные пользовательские input'ы, то без отдельного Controller'а не обойтись.
Бегло просмотрел лендинг PureMVC, но не успел пока что осознать. Завтра вечером посмотрю внимательней и опишу свои впечатления.
В PureMVC у всех составляющих есть четкий и довольно ограниченный круг обязанностей, но мне осталось непонятным, почему любому объекту разрешается общаться со всеми остальными через общий фасад? Вы уже применяли этот подход, поэтому хочу спросить, не запутывается ли логика в клубок из-за общение через фасад?
Рассматривать React только как UI компоненты можно, но действительно ли такая абстракция принесет пользу, мне осталось непонятным. Безболезненно можно будет перейти на другую UI-библиотеку, но часто ли такой переход осуществляется?
Насколько я понял, Фасад существует, в первую очередь, для инициализации приложения. И это фасад к фреймворку, не к приложению. Так в нем собраны методы для регистрации комманд, медиаторов и модели. Так разработчику проще инициализировать приложение. Не надо копаться во внутренностях самого фреймворка.
Вот пример от TodoMVC. Как видите здесь нет публичных методов как таковых, но используются методы фреймворка.
Вы уже применяли этот подход, поэтому хочу спросить, не запутывается ли логика в клубок из-за общение через фасад?
Т.к. фасад в логике самого приложения не участвует, то вся логика находится в командах. Команды группируются различными образами, можно положить несколько команд в команду-агрегат и т.п. Т.к. команды это отдельные классы, то и хранятся они в разных файлах(в AS3, Java). Соответственно не надо бегать особо по цепочке экшинов, а все наглядно дереве файлов в IDE.
Команд не может быть меньше чем логических действий в приложении + 1(StartUpCommand). По своей сути эти команды ближе к UseCase из Clean Architecture.
Разработчики React ведь сами позиционируют его как библиотеку и чисто View. Так что почему не использовать его так, как задумано? Плюсом мы получим Clean Architecture из коробки, ведь если над Реактом будет дополнительный слой, то там можно использовать Presenter, преобразовать все данные и оставить UI компоненты примитивными. А значит проще будет тестировать. Того же, в принципе, можно добиться c Redux. Только в качестве презентеров будут селекторы. Вы можете сами развить идею.
Безболезненно можно будет перейти на другую UI-библиотеку, но часто ли такой переход осуществляется?
Вот как раз таки в текущей ситуации, когда фреймворки обновляются каждый год, оно и надо.
Год назад мне достался проект на ангуляре 1.3. Рабочий бэкенд, не публичный. Конечно же все новые проекты у нас на Реакте. Переписать его весь и сразу — слишком много работы. А если мы поженились с фреймворком, и описали нашу логику в Реакт компонентах или Ангуляр компонентах, по надо именно что переписывать его целиком.
Слишком затратно и никто этого в здравом уме не будет делать.
При подходе PureMVC, я мог бы заменять UI компоненты по мере надобности. И выпилить ангуляр со временем.
Для бизнеса это значит, что не надо нанимать разработчиков для работы на супер старых версиях фремворков, которые никто уже не использует. И не надо раз в 2 года переписывать огромные проекты.
А для разработчиков — мы можем в разумных пределах пробовать новые библиотеки, заменять обратно не совместимые версии и т.п. Захотелось Vue — сделал страничку отдельную и никто не умер.
Вот как раз таки в текущей ситуации, когда фреймворки обновляются каждый год, оно и надо.
Понял это, когда проснулся на следующий день после написания комментария. Это действительно существенное преимущество.
Т.к. фасад в логике самого приложения не участвует, то вся логика находится в командах.
…
Команд не может быть меньше чем логических действий в приложении + 1(StartUpCommand). По своей сути эти команды ближе к UseCase из Clean Architecture.
Спасибо, теперь стало понятней.
Походя указан критически важный момент, которого многие не понимают.
MVC был придуман _очень_ давно, разделение между контроллером и видом там есть постольку, поскольку первый отвечает за одни устройства (ввод, мышь и клавиатура), а второй за другие (вывод, монитор). При этом в обоих случаях была нетривиальная логика (контроль перекрытия объектов, особенности рендеринга и т.п.) и она четко отделялась.
На данный момент юзер взаимодействует с интерфейсом, а не с устройствами ввода/вывода, который одновременно является и видом и контроллером (кнопка на веб-странице, например), при этом слой логики на 90% перешел во фреймворк.
Flux — это MVC, в котором роль Model играют Dispatcher и Store, а вместо вызова методов происходит отправка Action'ов.А вас не смущает, что на вашей схеме «правильного» MVC НЕТ «вызова методов»? А есть как раз отправка сообщений (Action'ов).
Заметили, как Controller проник во View на третьей строке компонента? Вот он:Нет, контроллер тут, если уж выделять «контроллер» по вашей схеме — это рантайм браузера, обслуживающий события мыши, плюс экшн-креэйторы, а в третьей строке вы просто запихали контроллерную логику во вью.
А вас не смущает, что на вашей схеме «правильного» MVC НЕТ «вызова методов»? А есть как раз отправка сообщений (Action'ов).
Не смущает, потому что отправка сообщения в Smalltalk == вызов метода в современных ОО-языке.
Нет, контроллер тут, если уж выделять «контроллер» по вашей схеме — это рантайм браузера, обслуживающий события мыши, плюс экшн-креэйторы
Так и есть, но зачем в текущей задаче и в 90% рядовых задач выносить эту логику в отдельное место? Не поймите меня неправильно, я не против декомпозиции и SRP, но я пришел к выводу, что YAGNI — самый мощный принцип, причем не только в программировании, но и во всех остальных аспектах жизни. Если контроллерная логика ва вью будет мешать нам разрабатывать или тестировать, мы вынесем ее в отдельное место. Software гибкое, оно все перенесет, не нужно боятся его менять.
но зачем в текущей задаче и в 90% рядовых задач выносить эту логику в отдельное местоЗатем, что код логики проще искать в отдельном месте, чем выискивать его посреди разметки представления, если у вас не на одну страничку кода весь компонент. А с подходом «давайте всё инлайнить без декомпозиции» непонятно, зачем вы тогда вообще заводите модель, а потом ещё что-то от неё наследуете. Почему просто component state не использовать? Он в компоненте уже есть «забесплатно».
Не смущает, потому что отправка сообщения в Smalltalk == вызов метода в современных ОО-языке.А в Redux отправка сообения == передача сообщения (экшна) методу dispatch. В общем, странно выглядит, когда про реально отправку сообщений прямо «по классике», к которой вы и апеллируете, вы пишете «вместо вызова методов».
Некоторые вещи из статьи мною остались недопоняты. Прошу пояснить.
Скажем есть у нас большое SPA. Стало быть задано множество различных сущностей, и, разумется, есть сложные взаимосвязи между ними. Согласно статье у нас 1 View может быть связан с 1-ой Model-ю. Но удержание всего костяка приложения в одной Model-и представляется мне мало реальным. Разве что будут Model-и, которые будет абстракциями над другими моделями, местами их композицией. В общем мой взбудораженный разум вырисовывает какую-то жутко сложную картинку (как обычно и получается, имхо, в больших приложениях, где нет архитектурных ограничений). Как такие вещи должны решаться по вашему мнению?
2-й вопрос касается производительности. В текущем виде у вас всем заправляет forceUpdate. Он вызывается руками при каждом обновлении. Т.к. нет ни pureComponent-ов, ни своих shouldComponentUpdate-ов, то это полный re-render всего VDom на любое изменение. В качестве вишенки на торте мы видим onClick={() => any}
. И правда, зачем тут суетиться, когда вокруг ад. Стало быть мы по боку пустили как производительность, так и реактивность. Не совсем понятно, зачем тогда нам вообще React. Ведь мы на выходе получили, что-то вроде Backbone + любой шаблонизатор. Сложное приложение по описанной выше схеме с React будет нежизнеспособным. Стало быть потребуются оптимизации и пересмотр подхода. Нужно будет уменьшить кол-во лишних render-ов по максимуму. И тут мы приходим либо к immutable-подходу, либо к observable. Оба радикально поднимут сложность приложения. Кажется от этого автор и пытается убежать.
Как мне показалось, в простых случаях redux приложения, хоть и громоздки, но весьма просты. И в 99% случаев ты прекрасно понимаешь что где лежит, как искать, как оно устроено. Но придётся пощёлкать. В сложных случаях всё примерно также, ибо есть масса ограничений, но в целях производительности у нас в ход идёт нормализация данных, сложные селекторы и прочая мемоизация, всякие трюки с организацией работы с большими списками. В качестве преимуществ redux подхода мы получаем то, что даже в очень большом приложении мы можем относительно легко за константное время разобраться как работает тот или иной механизм (за счёт one directional way и immutable structures это правда тривиально). В действительно больших приложениях это и правда очень жирный плюс. Платим же мы за это сложностью реализации новых "фич". То, что во Vue может потребовать пары строк, заставит в redux скачать по разным файлам, да и immutable правка данных это в некотором роде муки.
Другой распространённый подход это observables. Там свои нюансы. Если использовать что-то голое, вроде Knockout-а, то очень сложно не превратить большое и сложное приложение в огромного монстра, который переплетён столь причудливо, что простой смертный не сунется туда что-то распутывать. Возникают ситуации когда проще какие-то части кода выкинуть, чем отловить какой-то плавающий баг или понять какое-нибудь неочевидное поведение. Набравшись опыта и наевшись такого рода проблем, можно сформировать приложение уже по какой-нибудь конкретной архитектуре, продумав все фундаментальные ограничения по уму и тщательно и придерживаясь их, можно обуздать этого монстра. Но мне кажется, с первого раза это мало у кого получится. Как мне показалось, этот подход требует куда больших навыков архитектора от программиста, чем подход с redux.
К чему я веду. От сложности при написании больших SPA вам никуда не деться. И в данной статьей я не увидел никаких предпоссылок к тому, что сложность уменьшилась. Скорее наоборот. И такого рода приложения писали в большом количество. Я уже не первый, кто здесь упомянул Backbone.
В точку. Вся сложность обработки данных, их мутаций, все хитрые взаимосвязи, практически всё стоит за этим слоем. Сложность никуда не делась. От того, что теперь мы гордо называем это "Моделью" и не используем в ней редьюсеров жизнь малиной не стала, сложность организации такого кода не испарилась, и технический долг не перестал накапливаться вместе с ростом приложения. По сути говоря не раскрыв этот вопрос в деталях, я не понимаю, о чём тогда статья. О том, что ни на одном redux-е свет стоит? Вроде никто и не спорил.
Разве что будут Model-и, которые будет абстракциями над другими моделями, местами их композицией
Так и будет. В примере с TodoMVC TodoListModel содержит в себе список TodoItemModel. Но ведь модель списка задач и выглядит таким образом, как ее можно смоделировать иначе? Если модель предметной области сложная, то ее никуда не получится спрятать. Но здесь правильно будет задать себе вопрос: "Действительно ли модель мой предметной области такая сложная или я сам ее усложняю?"
Стало быть мы по боку пустили как производительность
onClick={() => any}
можно поправить, это небольшая беда. forceUpdate
не вызывает shallow compare, который происходит в shouldComponentUpdate
у PureComponent
. Поэтому разницу в производительности нужно замерять. Оценивая "на глаз", мы обычно ошибаемся
К чему я веду. От сложности при написании больших SPA вам никуда не деться.
Тут стоит задаться вопросом: "А действительно ли сайты и web-приложения, которым раньше с головой хватало Multi Page Application, нужно переводить на SPA? Может Turbolinks и MPA будут идеальными решениями для большинства наших проблем?"
Ну это вы пока со сложной логикой на редаксе не встречались. Не когда у вас пустяшный интерфейс вроде твиттера, а когда разные экшены десятками и сотнями летают. Распутать этот клубок становится очень сложно, потому что редакс никак не отражает последовательность происходящего, приходится прыгать по файлам и прослеживать логику каждого экшена.
> сложность организации такого кода не испарилась
Проблема же не в сложной организации кода, а в сложности доменной логики. Если логика сложна — вы либо пишете ее «влоб» (и получается запутанный клубок), либо заметаете часть сложности под ковер (в абстракции и их взаимосвязи), но по факту сложность никуда не денется и никак не уменьшится, иначе приложение просто не будет выполнять свою функцию.
Ну это вы пока со сложной логикой на редаксе не встречались
У меня сейчас под носом довольно сложное приложение. Ради интереса посмотрел, frontend-js файлы: 37k lines, 425 files. Приложению около года. Там довольно сложный рекурсивный UI, модульная компоновка. Не встречал описанных вами проблем. React и Redux позволяют мне получить полное древо компонент, просмотреть все данные полученные ими, проследить откуда они получены и как. Помимо прочего есть вспомогательные вещи, которые могут помочь отследить бесполезные render методы, найти бутылочные горлышки производительности. Я легко нахожу нужный мне reducer, ответственный за его изменение. Я легко могу сделать слепок приложения и отдебажить его в нюансах, шаг за шагом. Я легко могу прокрутить стейт вспять и вперёд. На самом деле у меня настолько большие возможности в этом деле. Периодически пользователи рапортуют мне о багах, за счёт имутабельности и отделённого от модели state поиск причины бага обычно не занимает больше 5 минут. Redux заставил меня полюбить функциональный подход к работе со сложным data-flow. Чем крупнее становится приложение, тем больше я люблю этот подход. Не зря в документации на каждой странице упоминается слово predictable. Это прямо в точку. Рискну предположить, что вы не правильно его готовите.
приходится прыгать по файлам и прослеживать логику каждого экшена.
Что здесь не так? Redux позволяет отследить изменения каждого такого action-а прямо в Redux Dev Tools. При правильной компоновке редсьюсеров не составляет никакого труда найти место, где это изменение осуществлено. К примеру у меня всегда у 1-го action-а строго 1 handler. Какой именно легко понять по префиксу action.type
.
Для сравнения, до этого я долго работал с большим приложением на Knockout-е. Я мог несколько дней убить просто, чтобы хотя бы воспроизвести баг, описанный пользователем. Последовательность отработки различных deferred и пр. observable и impure функции приводили порой к настолько сложным ситуациям, что в части случаев приходилось цеплять нелепые костыли (т.к. за разумное время решить проблему не представлялось возможным). И даже вопроизвести некоторые нюансы конкурентной обработки цепочек computed deferred observable на простом примере могло отнять пару часов.
иначе приложение просто не будет выполнять свою функцию.
В точку. И чем крупнее приложение, тем больше каждая вольность и вариативность будет сказываться на техническому долге и работе с приложением. Если каждая модель это отдельно взятая крепость, со своим уставом, то это может быть очень больно. В случае redux и др. подходов, при которых данные лежат отдельно, а всё остальное приложение является функцией render-а от этих данных, всё сильно упрощается. Но соглашусь в том, что намудрить можно везде. Мне показалось, что подход redux-а дисциплинирует делать всё единообразно и очевидно.
— Есть какая либо сложная бизнес-сущность (запись, статья, документ, не важно), у этой сущности около 100 свойств.
— Есть страница с выводом списка этих сущностей в таблице, по 100 и больше штук на страницу (На этой странице нужен только ID, Title и еще пара полей). Подгрузка новых осуществляется с помощью запроса на сервер.
— Есть так же страница с редактированием конкретной сущности (форма со 100 разными инпутами).
И у нас SPA React + Redux.
Теперь вопрос. Как в Redux правильно построить `store`? Запихнуть все в массив `records`? Но тогда на странице со списком получится 100 записей по 100 полей каждая, и куча выжратой непонятно для чего памяти. Или сделать 2 разных редьюсера `recordList` и `currentRecord`, но тогда нет единого хранилица, одни и те же данные получаются денормализованными, а редакс топит за нормализацию данных.
одни и те же данные получаются денормализованными
- Мало ли за что там топит redux. И нормализация и денормализация данных это инструменты. Как их использовать и когда решает разработчик.
- Класть в одну корзину разнородные данные, да ещё и таким образом, чтобы один элемент списка имел все поля, а остальные только по 2-3 поля — это создавать сложности на ровном месте.
Посему я бы просто держал в store отдельно light-список, и отдельно элемент с его блобами или что там у вас. И бровью бы не повёл.
В Redux так нельзя, потому что теряется единственный источник правды и ты предлагаешь говнокод
Ну человек либо понимает, что он делает, либо слепо следует написанному другим человеком. Проблема в том, что тот самый другой, скорее всего понимает, что его слова лишь рекомендации, которые имеют ограниченную сферу применения. А первый следует ему как пророку. Наступит на 10-ок граблей и будет думать более гибко ;) Не бывает истинно верных подходов. Мы должны думать своей головой и выбирать подходы/решения/инструменты по месту.
В Redux так нельзя, потому что теряется единственный источник правдыВот этого не понял. «Единственный источник правды» — это стор. Как он там внутри устроен — дело десятое.
Может ну его этот один стор и монолит?
Разные экраны — разные Реакт виджеты. У каждого свой стор. И не надо их смешивать.
В любом случае у фронтенд приложения нет базы данных, оно оперирует кэшами. База — она где-то в мускуле\динамо\монге\и тп. А кэши оптимизируются под конкретные нужды.
И это даже не противоречит идее единственного источника данных. Ведь для конкретного экрана — кэш действительно один. И даже лучше, это помогает с SRP и DDD. Ведь разные вещи остаются разными. И нет головной боли от того сколько полей где хранить.
Один records, в котором большинство значений в 4 поля, а одно (или просмотренные, если кэширование имеет смысл) в 100 полей и указатель currentRecord.
Мне кажется не стоит так поступать. Т.к. придётся заниматься тем, чтобы с течением времени список records не раздулся (пользователь постепенно открывал 100 записей). В общем множество плясок вокруг да около, ради одной только нормализации.
При смене текущей записи предыдущую можно чистить. Нужно, если не рассчитываете на кеширование.
Вопрос в целом решаемый. Можно и так. Но на мой взгляд, разные данные в разные корзины. Даже если часть полей у них формально общая. Сильно упрощается работа при таком подходе, и чище store.
Тут дело не в общих данных, а в том, что это одна сущность, это одни и те же данные. Обновили заголовок в форме редактирования — он должен обновиться в списке без нажатия ф5.
Ну денормализацию данных никто не отменял. В данном случае обновить эти несчастные 3 поля в двух местах, вместо одного, гораздо лучше, чем уродовать store и писать костыли :)
Я бы вообще рассматривал list и editItem компоненты как никак не связанные сущности. Посудите сами. Вы отредактировали какой-то элемент. И он у вас в списке обновился. Но другой элемент, которые отредактировал Вася, там всё равно не обновился. Если эти критично — вы так и так обновите список. Если нет, то о чём вопрос ;)
This.
>но тогда нет единого хранилица, одни и те же данные получаются
Ну запихните все записи в один объект по id и держите у каждой записи набор additionalFields и двумя селекторами `recordList` и `currentRecord` забирайте. Если для записи, показываемой на отдельном экране, нет набора additionalFields, то запрашивайте их сервера. А пока поля грузятся покажите заголовок из тех двух полей.
Просто скажите — сколько у вас тысяч экшенов?
> Что здесь не так?
То, что без редакса прыгать и отслеживтаь не надо (а когда надо — это элементарно делается средствами ИДЕ). У вас есть просто метод, который вызывает другие методы, последовательно и ясно. В случае редакса — у вас спавнится экшн, который спавнит другие экшны, которые вызывают изменения в сторе, при этом один экшн может менять информацию во многих сторах (иначе редакс становится бесполезен).
> Если каждая модель это отдельно взятая крепость, со своим уставом, то это может быть очень больно.
Так кто говорит про то, что каждая модель — своя крепость со своим уставом?
> В случае redux и др. подходов, при которых данные лежат отдельно
Это, с-но, и есть mvc. Что flux, что редакс — это варианты реализации mvc.
Редакс не имеет ничего общего с функциональным подходом. Фукнциональный подход — про чистые функции
Редакс не имеет ничего общего с функциональным подходом. Фукнциональный подход — про чистые функции
mapStateToProps, mapDispatchToProps, pureComponent, reducer-s, actionCreator-ы и пр. это что всё про impure? Или скажем разные виды мемоизации? Нет, конечно, pureComponent-ы бывают и со state-ом. Но это редкость. Чаще всего (если человек себе сам проблемы не создаёт) они как раз pure. Да практически весь stack — pure. Отдельные impure части собраны внутри react-redux. Не понимаю, что вы этим хотели сказать. То, что redux-подход не полностью pure? Это да. Но я обратного и не заявлял.
Логика работы редакса про impure. Сам стор работает при помощи грязных функций, с мутабельным изменением стейта.
Нет никакого закона который запрещает приложению состоять из кусков которые написаны в разных парадигмах. И лично меня интересует только тот кусок, который пишу я сам (или вынужден читать либо отлаживать).
Но вы не дергаете интерпретатор хаскеля и рантайм (за исключением каких-то специфических случаев). Он работает где-то там, незаметно и прозрачно, то есть вы всегда находитесь _внутри_. В случае же с редаксом — вызов impure диспатча, вызывающего мутабельное изменение стора, — _единственный_ способ работы со стором. И он вам необходим.
Впрочем, в redux-thunk и redux-saga эту проблему, кажется, решили.
Именно за счет функционального подхода в обход редакса, к слову.
> Вот с ней как раз проблема. Ее должна делать M, но как раз тот самый функциональный подход redux, в существование которого вы не верите, провоцирует переносить эту логику в C.
Как раз нет, провоцирует импьюрность редакса. В чисто функциональных сагах прекрасно все в модели остается.
Вам действительно нужен Redux?