Pull to refresh

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. В примере с чатом где-то в модели будет свойство "количество сообщений", на изменение которого подписываются вьюхи. Неважно почему оно изменяется, вьюхи будут получать сообщение, что оно изменилось.

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

P.S. Уверен, что вы лично это понимаете, но очень уж часто моделью называют именно класс, хранящий данные по типу ActiveRecord. Когда вижу что-то типа class User extends Model биться головой об стену хочется. :)
> но в рамках MVC подписываются они на события друг друга, как-то по другому взаимодействуют или совсем не зависят друг от друга никакого значения не имеет, для контроллера и вью — это одна модель.

Вид вообще и не в курсе о модели (и даже, возможно, о том, что она есть). Он просто предоставляет некоторое апи, по выводу информации пользователя, модель ее дергает. Либо вид может запрашивать у модели данные. У кого он запрашивает данные (и кто пользуется его апи) — не волнует никого.
Он просто предоставляет некоторое апи, по выводу информации пользователя, модель ее дергает

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

Она это знает, это же модель. В ней хранится вся информация о стейте приложения.

> Как в коде выглядит знание модели о вьюшке?

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

Ну в общем то же самое, что с моделью и контроллером.
Это не псевдокод. Вашу игру словами я, увы, не понимаю.
class view {
public viewMethod(...) {...}
}

class model {
public modelMethod() { view.viewMethod() }
}
Он не отвечает на те вопросы, которые я предусмотрительно задал.
На какие именно не отвечает, по-вашему? Вроде, на все, по списку. Что я пропустил?
Ну то есть в какой момент модель решает, что надо отрендерить ту или иную вьюшку? Как она решает, какую именно и какие данные ей будут нужны?
> Ну то есть в какой момент модель решает, что надо отрендерить ту или иную вьюшку?

Это вопрос ее спецификации.

> Как она решает, какую именно и какие данные ей будут нужны?

public viewMethod(...) {...} — какие аргументы во viewMethod, те данные и нужны. Спецификацию метода мы ведь знаем. Момент показа тоже известен из спецификации. Для определенности — пусть будет
class view {
public showCart(...) {...}
}

Этот метод может дернуть или модель (если надо показать корзину при определенных изменениях модели, каких именно — это спецификация модели), или контроллер (опять же, по спецификации, например, при клике на какую-то кнопку). Сам вид вообще в данном случае пассивен, он ничего не решает, а только предоставляет апи (содержит саму логику отображения корзины, но не отвечает за сам факт ее вывода), активна модель. MVC позволяет и другой подход — когда вид сам опрашивает модель и решает, что делать. И тот и тот подход допустим (но, наверное, их нежелательно смешивать).
UFO just landed and posted this here
> А зачем модели инстанс вьюхи вообще?

Если модель активная — то затем, чтобы обновлять view.

> Модель не может работать без вью совсем?

Может, если пассивная.
UFO just landed and posted this here
Да. Бросание ивента Х ничем не отличается от вызова соответствующего метода Х на вью, это ровно одно и то же. В обоих случаях у модели будет ссылка на инстанс вида, прямая или транзитивная.
UFO just landed and posted this here
UFO just landed and posted this here
> Нет, ибо в случае с ивентом модель не знает

Знает. У нее необходимо есть ссылка на вид, как она может не знать?
UFO just landed and posted this here
> Зачем ей ссылка на вью?

За тем, что объект А не может взаимодействовать с объектом Б если нет ссылки. Если вы отправляете некое сообщение кому-либо через подписку — у вас есть ссылка. Иначе бы сообщение не дошло.

> Вы ссылки на все эти вью будете пихать в модель?

Все пихают, чем я хуже? Если другого способа не придумано.

> В лучше случае у модели будет ссылка на ивент диспатчер

А ивент диспетчер имеет ссылку на вью. Таким образом, модель, транзитивно, имеет ссылку на вью. О чем я и говорю.
UFO just landed and posted this here
> Сообщение — это не прямая ссылка, это символьное описание того контекста метода, который вы собирайтесь активировать.

Да какая разница? Важно, что view.someMethod() это ровно то же самое, что dispatcher.notify(«someMethod»). В обоих случаях отправитель прибит гвоздями к апи получателя. Он должен знать это апи и понимать его семантику. И прямая у вас ссылка или транзитивная — тоже совершенно не важно.
UFO just landed and posted this here
> Да но получатели могут быть разными, и это не обязателью V

Так и в случае наличия ссылки на вид виды эти могут быть разными. Единственное требование — они должны предоставлять некий апи (реализовывать интерфейс). Это требование совершенно одинаковое как в качестве отправки сообщений, так и в случае вызова метода. В обоих случаях модель знает о спецификации данного апи, которое должно быть реализовано получателем.
UFO just landed and posted this here
> Да модель знает, но не о спецификации API.

Именно о них и знает. Вы не можете отсылать сообщение потребителю, если не знаете, какие сообщения он принимает и какова их семантика.

> В случае MVC должна соблюдаться независимость V от M в контекстном плане.

Еще раз, когда говорите о babylon-MVC, то называйте его babylon-MVC.

> Главное, чтобы методы одной части не влияли на методы другой.

А это невозможно. Если в области Х вызывается метод Y, то значит в этой области должна быть известна семантика данного метода.
UFO just landed and posted this here
> Диспетчер принимает все сообщения.

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

> Но управление или проброс осуществляется через контроллер.

Нет, не осуществляется. В MVC запрещено передавать данные из модели в контроллер. Это ключевая особенность MVC. Двунаправленный обмен данными может быть между моделью и видом и (в исключительных случаях) между контроллером и видом (обычно взаимодействие между видом и контроллером происходит через модель, она в данном случае выполняет функцию прокладки между ними, взаимодействие между контроллером и видом напрямую допускается, например, при подписке видов на модель). В случае же обмена контроллер->модель он строго однонаправленный.
UFO just landed and posted this here
> Или контроллер получая события определяет контекст ноды и обрабатывает ее хэндлером из соответствующего репо методов модели или вида.

Только не MVC, а MVP, и не контроллер, а презентер.
UFO just landed and posted this here
UFO just landed and posted this here
> Контроллер в классическом смысле, используется как источник данных для модели

Это уже вообще бредни. Никогда и нигде контроллер не используется как «источник данных». Источник данных в MVC сидит за моделью и самим MVC не описывается.
Кроме того, это противоречит даже вашему описанию выше — если контроллер находится _между_ моделью и видом, то уж точно он не может находиться _за_ моделью.

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

> Я же понимаю MVC так как я указал по ссылке. Я не помню когда первый раз услышал про MVC. PureMVC это был фреймворк который я юзал.

Ну то есть какой-то дурак сказал глупость, а вы теперь упорно ее повторяете.
UFO just landed and posted this here
> Правильно к модели. Что это как не источник? Но любые данные с клиента должны подтверждаться на стороне сервера

Та стрелочка — это не данные с клиента. Там вообще нету ни сервера ни клиента. У вас горячка.
UFO just landed and posted this here
> Это данные контроллера источающего данные для модели.

Я же говорю — у вас горячка.

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

Модель не знает об API view или controller. Она предоставляет им своё API, в частности она определяет механизм подписки и доставки сообщений об изменении своего состояния, которым они будут пользоваться. Она определяет его синтаксис и семантику. Вью при подписки на события модели должно строго соблюдать их, для адекватного отображения нужного ему среза стейта модели. Для модели же вью — абстрактный подписчик на её данные по её API, реализующий её интерфейсы типа /App/UI/MVC/Model/EventSubsriberInterface для подписки на её события /App/UI/MVC/Model/Event. Модель знает только как отправить такое своё событие подписчику, реализующему такой её интерфейс и прошедшему её флоу подписки на события. Вью это, логгер, транслятор событий во внешнюю MQ-систему — ей всё равно. Семантика вызываемых методов этого подписчика для модели ровно одна: "я сообщаю в удобной мне форме удобным мне способом подписчику, прошедшую удобную мне процедуру подписки, о том, что со мной случилось такое-то моё событие, в котором он высказал заинтересованность. Я своё дело сделала, обработает ли подписчик это сообщение с той семантикой, которую я в него вложила, мне всё равно".

> Модель не знает об API view или controller. Она предоставляет им своё API

Это зависит от конкретной реализации. Если вид получает от модели сообщения, то модель знает АПИ вида, по определению. Ведь она знает, какие сообщения ему слать и когда. А это и есть АПИ.
Но ведь вид реализует интерфейс, который предоставляет модель. То есть она знает свой интерфейс, а вид уже подстраивается
> а вид уже подстраивается

Подстраивается модель.
UFO just landed and posted this here
> Не, подстраивается вид

Мы уже это обсуждали. В этом случае ваше приложение просто не работает. Вам же нужно получить работающее приложение? Значит, вам придется подстраивать модель.
Например, вашему виду надо вывести некие данные. Модель эти данные не содержит. Вам надо изменить модель (подстроить под вид) так, чтобы она эти данные содержала. Еще вы, конечно, можете прям из вида сделать запрос и эти данные минуя модель получить. Но мы ведь такие варианты не рассматриваем в всерьез?

2VolCh
> Нет, сообщение, механизм подписки и механизм доставки — это API модели.

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

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


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

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

> В рамках MVC механизм доставки сообщений не выделяется, он или в модели, или в виде.

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

> Механизм может быть сторонний по факту, но входить он будет в 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 скорее всего будут намного более эффективны.

UFO just landed and posted this here
> Входит в рамках MVC.

Это ваша выдумка. Давайте будем понимать под MVC то, что под ним принято понимать, а не выдумки? Есть ведь статьи, в которых есть описание. Вы сами на эти статьи ссылались. А теперь несете отсебятину. Зачем?

> Отдельного слоя может и не быть, модель прямо с базой сожет работать.

Может и работать. Может вообще все во дну кучу быть свалено. Но в MVC в ответственность модели не входит работа с хранилищем, вот и все.

> Это роль архитектора. MVC — архитектурный паттерн.

MVC — это деталь реализации. Решение о применении MVC принимает программист, потому что принятие такого решения — часть непосредственно написания программы. Это задача программиста. Вы, конечно, можете называть его как угодно, архитектором или еще как.

> Да как же прибиваю, если модель знает о виде только то, что он реализует интерфейс, который является частью API модели?

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

> Не путайте данные и знания. Знания это как раз понимание формата данных.

В процитированном вами тексте ничего не было про знание в смысле понимания формата данных и прочие подобные вещи. Без отсебятины.

> В юнит-тестах я бы просто замокал один из модулей.

Так вы и замокали, по-этому у вас юнит-тест получился. В интеграционных assertInvokov не бывает.

> Но если виду хочется, например, чтобы модель отдавала ФИО в нескольких форматах, то это попытка нарушить границы ответственности.

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

> Нет. UI — это механизм взаимодействия системы и пользователя, а не система.

А любые требования к системе и формулирются исключительно в виде описания взаимодействия с пользователем. Как иначе-то?

> Банально в требованиях к UI не напишешь «при нажатии на кнопку должно отправляться письмо менеджеру».

Как это? Именно так и напишешь. Почему нет?

> UI не оперирует понятием «отправка письма», это понятие совсем другого уровня по сравнению с экранами, окнами, мышами и т. п.

УИ оперирует теми терминами, которыми оперирует пользователь. Если пользователь подразумевает отправку письма, то УИ оперирует терминами отправки письма. УИ общается с пользователем в понятных ему терминах, иначе это провал, знаете ли, в виде очень, ОЧЕНЬ плохого УИ.

> Это требование к системе. Оно будет декомпозировано на требования к модели «должно бросить исключение ZeroResidue при попытке добавления товара с нулевым остатком»

То, что кто-то будет куда-то пробрасывать исключение — деталь реализации, которая находится в ответственности программиста.
То есть, еще раз — аналитик формулирует требования ко взаимодействию системы с пользователем (то есть, к УИ). Потом программист (ну или если хотите называйте его архитектором) уже преобразует это к требованиям для конкретных модулей системы. Если выбрана MVC то требования аналитика впрямую преобразуются к требованиям вида/контроллера MVC, на основе этих требований к виду контроллеру формируются требования к модели (и, возможно, что всем или части этих требований модель _уже_ удовлетворяет). И потом приступаем к реализации.

> Слой взаимодействия системы с внешним миром не только UI.

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

> MVC — это про пользовательские интерфейсы, не про программные. Можно пробовать применять, но это использование не по назначению, другие методы организации API скорее всего будут намного более эффективны.

Ну как, вот есть у вас сервер, который принимает хттп-запросы и отдает хтмл-страницы. Это классическое MVC, вы хотите сказать, оно тут «не по назначению»?
UFO just landed and posted this here
UFO just landed and posted this here

Нет, сообщение, механизм подписки и механизм доставки — это 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 таки будет присылать события так вам хочется, но даже в этом случае я бы не стал на них рассчитывать.

UFO just landed and posted this here

Если модель дана как есть и какие-то EventEmitter'ы встроить не можем, то построил бы параллельное модели дерево прокси-объектов, фиксирующих вызов мутаторов модели, чтобы иметь после вызова мутатора полный список затронутых нод, чтобы перерендерить нужные виды.

UFO just landed and posted this here

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

UFO just landed and posted this here

Совсем не то же самое. У вас хардкорд, а я о динамическом, в рантайме определении вью, интересующихся той или или нодой модели.

UFO just landed and posted this here

Добавляем новый "геттер" в модель, дергаем его из вью. В вашей схеме ещё какой-то код надо будет писать?

UFO just landed and posted this here

Ну, собственно это классическая реализация MVC:


  • виды получают информацию из модели и подписываются на интересующие их события об её изменении и изменяют представляемую пользователю информацию по ним без ведома контроллера
  • контроллер транслирует события UI в сообщения (в мейнстрим ООП — вызовы мутирующих методов) модели без ведома вида
  • модель знает о контроллере только как о клиенте, который просит её изменить своё состояние, а о виде только как о клиенте, который просит у неё информацию о состоянии и информацию об его изменении. С контроллером отношения у неё чисто как у функции с вызывающим кодом, а с видом, как у публикатора событий с подписчиком. Ничего об их устройстве или апи она не знает.

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


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

UFO just landed and posted this here
Если у вас вид будет работать с моделью напрямую, то она будет зависеть от неё, и это не MVC

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

Там же, на википедии:
Среднестатистический ТТУК (контроллер) получал данные из БД (используя уровень абстракции базы данных, делая вид, что это модель) или манипулировал, проверял, записывал, а также передавал данные в Представление. Такой подход стал очень популярен потому, что использование таких контроллеров похоже на классическую практику использования отдельного php-файла для каждой страницы приложения

А также:
Контроллеры же, — как элементы информационной системы, — ответственны лишь за:

— приём запроса от пользователя;
— анализ запроса;
— выбор следующего действия системы, соответственно результатам анализа

Нигде не сказано, что контроллер каким-то образом выступает как прослойка между моделью и вьюшкой.
UFO just landed and posted this here
UFO just landed and posted this here
У тебя просто от старости мозг усох, дедуля, раз опустился к апелляции к возрасту. Твой возраст, кстати, скрыт в профиле.
UFO just landed and posted this here
У тебя я бы даже время не спрашивал.
UFO just landed and posted this here

Клиент зависит от сервиса, а не наоборот. Если я в виде пишу user.getFullname() то это вид зависит от модели, а не наоборот.


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

UFO just landed and posted this here

Не понимаю зачем отслеживать изменения в видах. Если даже есть у вида какой-то свой стейт, то это его исключительно внутренний стейт.


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

> Вью и Модель работает с Контроллером, а не друг с другом.

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

> У меня он не только терминальный. Я и на Модель и на Вид имею свой вид:)

Вы можете придумать любую архитектуру и назвать ее, например, «babylon-MVC». Но если вы говорите просто «MVC», то извольте использовать общепринятое определение, иначе вас просто люди не поймут.
UFO just landed and posted this here
UFO just landed and posted this here
Я использую 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) ,
)
Кажется, вы пытаетесь сделать $mol из rx — отсюда и такие «типичные» костыли…
> Не знаю что делает shareReply, но Rx надо использовать по минимуму, тогда не будет этих проблем.

Это как раз неверно. Если rx используется — следует использовать его максимально (или вообще не использовать), иначе как раз и будет куча граблей.

> Оказалось, что из-за того, что у shareReply не было сабсрипшенов, то события пролетали в /dev/null не доходя до него. А когда сабскрипшены появлялись, то событиям в стриме уже было не от куда взяться.

shareReplay(n) эмитит последние n значений при подписке. Вы либо просто не прочитали описание функции, либо что-то выдумываете. Либо под дурочка косите.

> Типичный костыль с Rx сейчас у меня выглядит так:

Если он у вас типичный, то в чем проблема выделить его в отдельную пайп-функцию и горя не знать?

mayorovp я всего лишь решаю проблемы, которые давно решены в $mol_atom, MobX и куче других библиотек, но услужливо рабросаны по всем Rx-у.


Это как раз неверно. Если rx используется — следует использовать его максимально (или вообще не использовать), иначе как раз и будет куча граблей.

Да там человек топит за redux. Мол, Засунем весь стейт в глобальную переменную, обложимся экшенами и на каждый чих будем синхронно пушить состояние во все компоненты. А я только впендюрил ChangeDetection.OnPush везде, чтобы этого не происходило, ибо всё страшно тупило.


shareReplay(n) эмитит последние n значений при подписке. Вы либо просто не прочитали описание функции, либо что-то выдумываете. Либо под дурочка косите.

Очень легко строить из себя умного, когда вам расписали пробелему и почему она происходит. Вот инвестигировать её не так-то просто, когда после рефакторинга тесты начинают валиться со странными сообщениями. И проблема тут не в 3 тупых головах, а в инструменте, который вместо того, чтобы гарантировать инварианты над состояниями, заставляет играть в railroad tycoon прыгая между файлами и засекая кто когда подписывается и кто когда отправляет данные.


Если он у вас типичный, то в чем проблема выделить его в отдельную пайп-функцию и горя не знать?

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

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

Не надо строить из себя умного, надо просто читать документацию. Если используете ф-ю — ну прочитайте как она работает. Тогда не будет неожиданностей. Ведь то, о чем вы пишете, это не какие-то специфические особенности потрохов, на которые вы случайно наткнулись — это базовое поведение.
Ну, раз вы такой умный и начитанный, то наверняка знаете, почему вместо shareReplay нужно использовать комбинацию из publishReplay и refCount?
Так вот как раз по этим причинам, не нужно. Нужно использовать как раз shareReplay, если хотите консистентное поведение.
При чем тут текущая память? ShareReplay так и должен работать. Это _до фикса_ он работал неправильно и отписывался, когда отписываться не надо. Чем вам удивительно текущее (корректное) поведение совершенно непонятно. Та подписка, от которой вы отписались — она отписана, все окей. Во втором случае — это отписка от сабжекта, созданного при помощи shareReplay(). Подписка же самого shareReplay на оригинальный барклик никуда не девается и не должна.
Вы выдумываете какие-то мокрые мечты о том, как что-то должно работать (причем мечты неконсистентные), и потом обвиняете кого-то в том, что ф-я ведет себя корректно вместо того, чтобы быть сломанной. Ну это что-то совсем странное, согласитесь.
Зачем вы читаете документацию 4 версии, если речь о фиксе из пятой? Признайтесь честно, вы троллируете?

> Или хотя бы подумали, в каких случаях может потребоваться, чтобы память текла по умолчанию.

Никакая память не течет, не выдумывайте. Все подписки освобождаются, как и должно быть.
Затем, что вы утверждаете, что в 4 поведение было ошибочным и в 5 его «починили».

А, ну раз вы так сказали, то никаких утечек нет, ага.

До чего фронтендеры докатились. Уже утечки памяти считают нормальным явлением. И правда, кому могут потребоваться приложения, которые могут работать дольше полу часа без перезагрузки?
> Уже утечки памяти считают нормальным явлением.

Зачем вы врете? Там нет никаких утечек. Если бы использование методы действительно давало утечки, уже бы был сделан фикс.

Утечка — это когда у вас есть ресурс, который висит и который нельзя освободить (например, память которую вы выделили, и на которую просрали указатель). В рассматриваемом случае вам никто не мешает все требуемые подписки закрыть. Если вы, конечно, САМИ этого не сделали — то будет утечка, но кто кроме вас виноват? Вы бы еще new назвали утечкой само по себе.

> Затем, что вы утверждаете, что в 4 поведение было ошибочным и в 5 его «починили».

Все так и есть. В 4 поведение было ошибочным. Метод не должен съедать буфер и не должен закрывать подписку сабжекта на оригинальный обсервабл, потому что это подписка вообще вне его юрисдикции. Было бы поведение правильным и ожидаемым — никто бы его не менял.

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

Проблема в том, что там нет никаких утечек. Просто есть метод, который до фикса работал неправильно (и неконсинстентно с тем же share), а теперь работает правильно и консистентно. Так вышло, что теперь он не убирает подписки, которые изначально _и не должен_ был убирать (их надо было убирать самому). Сделали фикс, чем нарушили обратную совместимость, люди, которые закладывались на исправленное неконсистентное поведение закономерно недовольны. А vintage просто троллирует.
Уважаемый автор, рискну предположить что на вас здесь не поймут, ибо вы затрагиваете вопросы религии. :) Я поясню о чем я:

Проблема в том что в мире современной разработки в среднем по индустрии возраст разработчика 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). Все еще будет легкочитаемо, без приложения в виде графа состояний?
Погодите, как экшны могут что-то «спавнить»? Экшн — это объект с данными, который отправляется диспетчеру. Вы про action creator'ы, может быть?
> Экшн — это объект с данными, который отправляется диспетчеру.

Почти верно. Экшн — это все, что отправляется диспетчеру, ну то есть все, что можно засунуть в ф-ю dispatch. В том числе — все то, что может быть обработано подключенными middlewares. Асинхронные экшоны, например. Вообще, без экшонов, которые спавнят экшоны (в том или ином виде), вы никакую нетривиальную логику реализовать просто не сможете.
без экшонов, которые спавнят экшоны (в том или ином виде), вы никакую нетривиальную логику реализовать просто не сможете.
А экшн-креэйторы на что?
> Это я к тому, что реакт довольно специфично работает под капотом и вряд ли virtual dom — старое и хорошо забытое.

virtual dom — это обычный буфер. Понятию буфера (а так же принципам работы с буферами того или иного вида того или иного назначения) не один (и не два и даже не три) десятка лет. Более того — операции с домом буферизовывались задолго (очень задолго) до того как фейсбук вообще появился.
UFO just landed and posted this here
Это я к тому, что… что за предвзятость на хабре? Это, как минимум, не профессионально

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

Сильное заявление, проверять я его конечно же не буду.

Стандартный вопрос на собеседовании: почему так делать не стоит и как это писать правильно.
Согласен на 100%

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

Вот по сути конкретного вопроса (фронт-энда). В вебе был тонкий клиент потому, что устройства были слабые и возможностей делать что-то больше, чем добавить пару элементов или чем-то пошевелить не было. Потом появились устройства мощнее, но то, как это все строить правильно уже давно решено в Java Swing/AWT, WPF, GTK, Qt и прочих. Но как правильно подмечено, большинство современных программистов и особенно на фронте даже не пытаются получить нормальное образование.

P.S. На днях опросил 4 человек и 2 из них не знали что такое кэш-промахи, 3 сеньор и один выше (он тоже знал).
github.com/gothinkster/realworld это интересный проект который реализует разными библиотеками и фреймверками фронтенд и бэкенд приложения упрощенного medium.com Там есть готовые примеры в частности и на react/redux. По ощущению от работы мне очень понравился проект на elm но его код слишком сложен и необычен. В issue этого репозитария есть еще больше приложений разной степени готовности (в частности есть и моя реализация react(redux)-hot-universal на стадии ревью кода).

Этот проект как раз ответ бесконечным ToDoApp которые так красиво делать на vanilla backbone.

(«А теперь перейдем к чему-то более практичному. Предположим Ваш марсолет должен ....»)
Посмотрел оттуда github.com/gothinkster/react-redux-realworld-example-app Извините конечно, но 20 компонентов и 8 редьюсеров это не Real World Application. Во всем проекте несколько тысяч строк кода в целом. Это вообще ни о чем. Там даже разделение кода на контексты не выполнено. Все свалено в src как в любых HelloWorld-ax. Реальное бизнес приложение как правило включает десятки а иногда и сотни модулей размером, как код всего этого примера.
Даже live пример посмотрел, мы в начале 2000-ных еще до выхода jQuery писали проекты в разы сложнее чем этот «real world example».
Ну да это же замена todoapp. Мне сложно представить какой тестовый проект можно создать и привлечь сотню разработчиков чтобы протестить продукт который к моменту окончания проекта утратит актуальность. Кстати что такое разделение приложения на контексты?
Мне сложно представить какой тестовый проект можно создать

CRM, или ERP или даже более менее сложная CMS. И я не говорю про тестовый, может где то есть OpenSource CRM/CMS построенная на React + Redux как продукт. Хотелось бы взглянуть на такой проект, как там все устроено и на сколько Redux помог.

Кстати что такое разделение приложения на контексты

martinfowler.com/bliki/BoundedContext.html
Я долго пытался найти что-то хорошее опенсорсное в от ERP до CMS и не нашел пока что. Для PHP лучшим выбором по CMS сегодня это по-прежнему Drupal. А на супер-хитовом nodejs вообще нет ничего подобного чтобы работало. Те CMS которые я примерно год назад пытался прспособить как каркас для разработки по функционалу весьма ограничены и по производительности просто нерабочие (на реальныз сайтаз явно блокируется движок JavaScript и сайты нерабочие). Это и не удивительно. наверное прошло время когда могли зародиться такие вот проекты энтузиастов (Drupal, Wordpress). Сейчас в опенсорс попадают продукты немного другим путем — через коммерческие организации (react, go, tarantool, openresty, lapis) и цель насколько я могу понять это получение базы для тестрования и новых идей для разработки. Но что касается готовых коммерческих продуктов на важные для бизнеса темы это уже за отдельную плату.
UFO just landed and posted this here
> не говоря уже о том факте что проблемы действительно сложных интерфейсов решались и были так или иначе решены еще до их рождения.

Ой, ну что вы! Куда там тем дряхлым «сложным» интерфейсам до сверхзадачи вида отрисовки одного счетчика в двух местах :)

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

Но тогда ведь придется разбираться, зачем инструмент нужен (какую проблему решает), вместо того чтобы просто следовать хайпу :(
Как итог, изменение требований к более или менее средней форме или куску интерфейса занимает несколько недель и то и месяц разработки (в попытках понять, а как же это сделать на Redux + React + Saga + Thunk + Redux Form +… «правильно» и чтобы работало. Разработка превращается в забивание костылей, потому что «так не работает», вот так «нарушен источник правды», ну а так «в реакте не принятно»),

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

Вот насчет redux-form полностью соглашусь. Имею один react проект и главная проблема (самый запутанный код) там именно в redux-form. К сожалению каких-то альтернатив (кроме как реализовать форму полностью самому) не вижу. И да, в маленьких, простых формах redux-form работает прекрасно.

UFO just landed and posted this here

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'ы, но в этом случае некоторые моменты работали не корректно.

Основная проблема состоит в том что начальные значения нужно передавать при создании формы и они передаются самой форме и должны повторять структуру формы.

Если данные пришедшие с сервера повторяют структуру формы, то у архитектора данного решения каша в голове и полностью отсутсвует понимание «инкапсуляции» и разделения системы на слои и ограниченные контексты.
UFO just landed and posted this here

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


Напишу лишь пример реального проекта, вы просили его. Я сейчас являюсь одним из фронтенд разрабов проекта https://rawg.io


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

зайди на твич, они на react-redux
UFO just landed and posted this here

Эм нет. Ну т.е. Да, но нет. Короче — это редуксовский антипаттерн, а не то как его нужно готовить. Ну например когда вы захотите это тестировать — вам придётся каким то образом это инжектить. Да и вообще зачем вообще тогда редукс нужен?

UFO just landed and posted this here

На мой взгляд mobx тут на порядок удобнее при таком же применении. Основа редукса — отсутствие сайд эффектов, которая там про тестирование. Отсюда весь геморрой с экшенами. Если уж забивать на три столба редукса — так по полной. Гулять так гулять, как говорится. Хотя в целом идея мне нравится, но вот правда интеренсо как тестировать без инжектов, поделитесь?

UFO just landed and posted this here

C MobX вам никто не запрещает хранить все данные в одном сторе.

UFO just landed and posted this here

Как я понимаю, даже если написать подобное, то никаких преимуществ MobX не обнаружится. У него идеология другая — нет какого-то абстрактного хранилища, в которое можно положить какой-то абстрактный стейт и манипулировать им. Есть вполне обычные объекты, инстансы обычных классов (использовал только с ES6 классами, можно и с ES5 объектами, но сам не пробовал) со своим стейтом, управлямым методами класса (сеттерами, мутаторами), но клиент класса вместо обычного получения среза состояния может подписаться на доставку этого среза, начальную и последующих изменений. MobX запомнит граф объектов, который участвовал в получении среза и при их изменении доставит новый срез.


Сравнивать подобным образом можно на каких-то простых приложениях

UFO just landed and posted this here

Вью отображает какой-то срез стейта независимо от того где и как хранится стейт. Стейт, хоть с редакс, хоть с мобкс представлен графом объектов. Просто в редакс эти объекты вырожденные, только состояние, да ещё принято ссылки между объектами реализовывать с помощью ключей и/или денормализовывать граф. А в мобкс полноценные объекты с поведением и связи выражаются обычными JS-ссылками.

UFO just landed and posted this here

Граф модели нормализован с ссылочными связями, в редакс его принято или денормализовывать, или делать связи через значения ключей.

UFO just landed and posted this here

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


{
  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-граф.

UFO just landed and posted this here
UFO just landed and posted this here

Тут не в текстовом представлении дело, json мог быть денормализованным в примере, а в способе нормализации самого хранилища. Хранилище Redux не поддерживает нормализацию, оставляя её полностью на откуп разработчика и, как минимум, не рекомендуя использовать для неё нативные объектные ссылки вне пределов одного дерева. А сами ноды хранилища — вырожденные объекты без поведения. А в MobX поддерживается и рекомендуются нормализация через объектные ссылки. Хранилище MobX — самодостаточный нормализованный граф полноценных объектов.

UFO just landed and posted this here

Полноценные объекты — объекты с собственным поведением, с методами, с функциями. Если у вас require("./lib/methods") возвращает функцию, то такого не должно быть в хранилище редакс и такое не сериализуется в json. Вернее можно попытаться сериалзировать исходники для последующего eval например, но это точно не редакс-вэй.

UFO just landed and posted this here

Например, "Объект в программировании — некоторая сущность в компьютерном пространстве, обладающая определённым состоянием и поведением, имеющая заданные значения свойств (атрибутов) и операций над ними (методов)" Нет методов или нет свойств — это уже не полноценный объект.

UFO just landed and posted this here

Для меня объект — это прежде всего основное понятие ООП, независимо от языка. Хэш-таблица там под капотом или вычисленные на этапе компиляции целочисленные смещения — дело десятое, пока не встаёт вопрос производительности. Основной интерфейс объекта — его методы, даже если свойства де-факто не сокрыты. Сериализация/десериализация (вернее нормализация и денормализация стейта) — ответственность самого объекта в общем случае.

Хотелось бы посмотреть пример такого же кода, как в моем репозитории, только на 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 ) выглядит как магия тому, кто не знаком с синтаксисом.

В том и суть, что они обрабатываются автоматически и не требуют ручной работы. Можете поиграться с живым примером: 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 там не главное. Его можно воспринимать как опциональный мета-язык описания наследования и патчей.

И без tree части mol дают автоматизацию многих вещей: наследование стилей, обработка loading/error по-умолчанию, упрощение работы с асинхронностью — код, выглядит как синхронный, без async/await и промисов, с возможностью retry в случае ошибки, типобезопасные контексты.
Ну вот когда примеры будут без tree — тогда его можно начать как-то воспринимать
> В том и суть, что они обрабатываются автоматически и не требуют ручной работы.

А если нужно кастомный loading и error?
Участок кода, loading/error которого надо обрабатывать, обертывается в try/catch.
Ну так можно увидеть эквивалентный код, с кастомной обработкой?
Примерно так:
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() )
}

}
Не люблю абстрактные примеры в вакууме, насколько я понял началось все с примера на mobx, который кстати не будет нормально работать, т.к. token после обновления страницы пропадет. А на атомах аналогичный будет:
////////////////// 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.
try/catch внутри рендера? Блестяще, просто восхитительно.
Замаскировать try/catch под jsx несложно.
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, где в примерах все очень похоже.
А если мне надо в разных местах перехватывать разные ошибки и loading и по-разному реагировать, то что делать?
Принцип я описал — «как на ексепшенах», остальное — дело техники и конкретных требований. Никто не даст готового 100% универсального решения.
Например, в 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>
  }
}

> Никто не даст готового 100% универсального решения.

Ну как же, вариант с 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. В

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

> Действует принцип — все, что не обработано внизу — попадает наверх.

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

> Есть класс задач, которые удобнее решать исключениями или их имитацией.

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

Смущает, конечно.
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

1) Логировать можно с помощью классического паттерна декоратор: https://en.wikipedia.org/wiki/Decorator_pattern
3) Изменять свойство другого класса можно. Методы будут храниться там, где вы посчитаете нужным в зависимости от композиции своих объектов.
4) Вот так, например


export class Application {
    @observable user = new User();
}
UFO just landed and posted this here

А что не так с декораторами и 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>
        )
    }
}

Если хочется логировать глобально все события, то можно использовать spy, который идет вместе с Mobx.

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;
   }
}
UFO just landed and posted this here
Но если сравнивать с 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`?
actions/authentication.js
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 замокать. Неудивительно, что вы всё-всё логаете, у вас там черт ногу сломит. Что-то откуда-то берется, что-то куда-то пишется при помощи чего-то, что вызывается.
UFO just landed and posted this here
появились какие то обиды и оскорбления

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

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

И что? Это не отменяет часть других аргументов. Вы ведь процитировали выборочно, так, чтобы было что ответить.

Более того, зависимости в ООП принятно передавать через конструктор и управлять ими через контейнер, а у вас как то по колхозному…

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

Опять ошибаетесь.

Окей, тут вы правы. Я невнимательно прочитал конец доки (уж извините, код не из приятных), так что инъекция в стиле redux-thunk у вас, похоже, поддерживается.

А многое вы просто пропустили мимо.

Скажу честно, лично мне ваш код нравится больше, чем оригинальный редакс. Но у вас совершенно не redux-light, он очень сильно не похож на редакс архитектурно, хотя похож внешне. В этом, кстати, проблема. Те, кто любит редакс — не захотят им пользоваться, потому что вы убрали важные вещи, а те, кто не любит — не захотят, потому-что слишком похож на redux и перенял слишком много его недостатков.
UFO just landed and posted this here
> Да и вообще зачем вообще тогда редукс нужен?

Редакс в принципе нужен для двух вещей:
1. доступ к единому, глобальному состоянию
2. доступ к единому, глобальному потоку событий

А как же единая точка изменения этого состояния?

Точек изменения как раз много — ведь практически каждая точка спавна экшена является таковой.
Не согласен, точка изменения — редюсер, получающий экшен, а не точка, в которой этот экшен генерируется.
> Не согласен, точка изменения — редюсер, получающий экшен, а не точка, в которой этот экшен генерируется.

Редьюсер действие описывает. А .dispatch() — это действие вызывает. Если у вас есть метод, который изменяет глобальное состояние, то точка изменения состояния — любой вызов данного метода.

Короче, «точка изменения» — это любой участок кода, при вызове которого состояние меняется.

Сорри, да. dispatch точка изменений, но не генерация экшена или вызов dispatch.

Но зачем он такой нужен?
UFO just landed and posted this here
По поводу судьбы redux — есть совсем новый доклад от Дэна Абрамов www.youtube.com/watch?v=CWXkcAe_mJA я его даже встрчал на этом сайте с переводом сейчас не могу найти возможно удалили. В свете предстоящих изменений redux вполне может стать неактуальным.

Самая большая трабла с redux/react это асинхронная загрузка данных с сервера то есть то что присутсвует на каждой странице. У Вас фактически есть два возможности реализации
1) Пред-загрузить данные с сервера и после это отристовать компонент (сложновато)
2) Сделать отрисовать компонент-заглушку и после этого реальный компонент с данными (отять же трудоемко в каждом компоненте программировато два стстояния с данными и без данных) И в этом случае страница будет «прикольно» прыгать по мере загрузки данныхв компонент что мы модем наблюдать на сайтах даже на самых авторитетных типа NYTimes.

Новые возможности которые планируются в react должны порешать эту проблемы и как раз в основном на решение этой проблемы и направлены. Но redux в эту схему как-то не вписывается.
И в этом случае страница будет «прикольно» прыгать по мере загрузки данныхв компонент что мы модем наблюдать на сайтах даже на самых авторитетных типа NYTimes.

В случае с NYTimes будет уместно задать вопрос: "Зачем вам Single Page Application, если вы не сильно сложнее, чем Wikipedia и можете использовать Multi Page Application?"

Наверное потому что там не последнюю роль играет Джереми Ашкеназ (автор backbone.js)
Спасибо за статью. Согласен с автором, про необоснованную сложность редукса. Уже года полтора пытаюсь донести до наших фронтедщиков, что это зло во плоти. Да времени небыло запилить хороший контр пример.
Хотел уточнить, в примерах, вы специально упростили, слив вместе контроллер и вью? Рассматриваете ли вы подход из 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.

Спасибо, теперь стало понятней.

Для уменьшений бойлерплейта в Redux запилили простенькую библиотечку redux-symbiote. Всем рекомендую хотя бы ознакомиться
По мне так redux довольно прост, в том плане в котором имеет ввиду автор…
> Наши React-компоненты превратились не просто во View, а в тесно связанные пары View-Controller

Походя указан критически важный момент, которого многие не понимают.

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.

1. Собственно по MVC модель является фасадом для контроллера и представления, за которым прячется сколь угодно сложная логика как угодно декомпозированная. За ним прячется всё, что не касается UI.

В точку. Вся сложность обработки данных, их мутаций, все хитрые взаимосвязи, практически всё стоит за этим слоем. Сложность никуда не делась. От того, что теперь мы гордо называем это "Моделью" и не используем в ней редьюсеров жизнь малиной не стала, сложность организации такого кода не испарилась, и технический долг не перестал накапливаться вместе с ростом приложения. По сути говоря не раскрыв этот вопрос в деталях, я не понимаю, о чём тогда статья. О том, что ни на одном redux-е свет стоит? Вроде никто и не спорил.

Разве что будут Model-и, которые будет абстракциями над другими моделями, местами их композицией

Так и будет. В примере с TodoMVC TodoListModel содержит в себе список TodoItemModel. Но ведь модель списка задач и выглядит таким образом, как ее можно смоделировать иначе? Если модель предметной области сложная, то ее никуда не получится спрятать. Но здесь правильно будет задать себе вопрос: "Действительно ли модель мой предметной области такая сложная или я сам ее усложняю?"


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

onClick={() => any} можно поправить, это небольшая беда. forceUpdate не вызывает shallow compare, который происходит в shouldComponentUpdate у PureComponent. Поэтому разницу в производительности нужно замерять. Оценивая "на глаз", мы обычно ошибаемся


К чему я веду. От сложности при написании больших SPA вам никуда не деться.

Тут стоит задаться вопросом: "А действительно ли сайты и web-приложения, которым раньше с головой хватало Multi Page Application, нужно переводить на SPA? Может Turbolinks и MPA будут идеальными решениями для большинства наших проблем?"

> В качестве преимуществ redux подхода мы получаем то, что даже в очень большом приложении мы можем относительно легко за константное время разобраться как работает тот или иной механизм (за счёт one directional way и immutable structures это правда тривиально).

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

> сложность организации такого кода не испарилась

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

У меня сейчас под носом довольно сложное приложение. Ради интереса посмотрел, 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`, но тогда нет единого хранилица, одни и те же данные получаются денормализованными, а редакс топит за нормализацию данных.
одни и те же данные получаются денормализованными

  1. Мало ли за что там топит redux. И нормализация и денормализация данных это инструменты. Как их использовать и когда решает разработчик.
  2. Класть в одну корзину разнородные данные, да ещё и таким образом, чтобы один элемент списка имел все поля, а остальные только по 2-3 поля — это создавать сложности на ровном месте.

Посему я бы просто держал в store отдельно light-список, и отдельно элемент с его блобами или что там у вас. И бровью бы не повёл.

Вот вот, и тут же начинаются крики типа "- В Redux так нельзя, потому что теряется единственный источник правды и ты предлагаешь говнокод.". Объяснить это становится очень сложно, потому что начинают тыкать в библию и example. А если еще и сменить структуру каталогов со стандартной «actions/reducers/components» и попытаться делить по бизнес контекстам, так вообще заклюют… :)
В Redux так нельзя, потому что теряется единственный источник правды и ты предлагаешь говнокод

Ну человек либо понимает, что он делает, либо слепо следует написанному другим человеком. Проблема в том, что тот самый другой, скорее всего понимает, что его слова лишь рекомендации, которые имеют ограниченную сферу применения. А первый следует ему как пророку. Наступит на 10-ок граблей и будет думать более гибко ;) Не бывает истинно верных подходов. Мы должны думать своей головой и выбирать подходы/решения/инструменты по месту.

В Redux так нельзя, потому что теряется единственный источник правды
Вот этого не понял. «Единственный источник правды» — это стор. Как он там внутри устроен — дело десятое.
Мне недавно пришла идея, что «не все так однозначно» :)
Может ну его этот один стор и монолит?
Разные экраны — разные Реакт виджеты. У каждого свой стор. И не надо их смешивать.
В любом случае у фронтенд приложения нет базы данных, оно оперирует кэшами. База — она где-то в мускуле\динамо\монге\и тп. А кэши оптимизируются под конкретные нужды.
И это даже не противоречит идее единственного источника данных. Ведь для конкретного экрана — кэш действительно один. И даже лучше, это помогает с SRP и DDD. Ведь разные вещи остаются разными. И нет головной боли от того сколько полей где хранить.

Один records, в котором большинство значений в 4 поля, а одно (или просмотренные, если кэширование имеет смысл) в 100 полей и указатель currentRecord.

Мне кажется не стоит так поступать. Т.к. придётся заниматься тем, чтобы с течением времени список records не раздулся (пользователь постепенно открывал 100 записей). В общем множество плясок вокруг да около, ради одной только нормализации.

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

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

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

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

И начинаются сложные зависимости, для ухода от которых вроде редакс создан.

Ну денормализацию данных никто не отменял. В данном случае обновить эти несчастные 3 поля в двух местах, вместо одного, гораздо лучше, чем уродовать store и писать костыли :)


Я бы вообще рассматривал list и editItem компоненты как никак не связанные сущности. Посудите сами. Вы отредактировали какой-то элемент. И он у вас в списке обновился. Но другой элемент, которые отредактировал Вася, там всё равно не обновился. Если эти критично — вы так и так обновите список. Если нет, то о чём вопрос ;)

То, что Вася обновил мне до лампочки, а тут "ничего не работает" :)

Как вариант — можно иметь 1 список полных данных о документах, со всеми полями, за которыми нужно следить и обновлять, а также отдельные поля в сторе чисто для хранения некоторой выборки айдишников для нужной страницы
>Или сделать 2 разных редьюсера `recordList` и `currentRecord`
This.
>но тогда нет единого хранилица, одни и те же данные получаются
Ну запихните все записи в один объект по id и держите у каждой записи набор additionalFields и двумя селекторами `recordList` и `currentRecord` забирайте. Если для записи, показываемой на отдельном экране, нет набора additionalFields, то запрашивайте их сервера. А пока поля грузятся покажите заголовок из тех двух полей.
> У меня сейчас под носом довольно сложное приложение. Ради интереса посмотрел, frontend-js файлы: 37k lines, 425 files. Приложению около года. Там довольно сложный рекурсивный UI, модульная компоновка. Не встречал описанных вами проблем.

Просто скажите — сколько у вас тысяч экшенов?

> Что здесь не так?

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

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

Так кто говорит про то, что каждая модель — своя крепость со своим уставом?

> В случае redux и др. подходов, при которых данные лежат отдельно

Это, с-но, и есть mvc. Что flux, что редакс — это варианты реализации mvc.
> Redux заставил меня полюбить функциональный подход

Редакс не имеет ничего общего с функциональным подходом. Фукнциональный подход — про чистые функции
Редакс не имеет ничего общего с функциональным подходом. Фукнциональный подход — про чистые функции

mapStateToProps, mapDispatchToProps, pureComponent, reducer-s, actionCreator-ы и пр. это что всё про impure? Или скажем разные виды мемоизации? Нет, конечно, pureComponent-ы бывают и со state-ом. Но это редкость. Чаще всего (если человек себе сам проблемы не создаёт) они как раз pure. Да практически весь stack — pure. Отдельные impure части собраны внутри react-redux. Не понимаю, что вы этим хотели сказать. То, что redux-подход не полностью pure? Это да. Но я обратного и не заявлял.

> mapStateToProps, mapDispatchToProps, pureComponent, reducer-s, actionCreator-ы и пр. это что всё про impure?

Логика работы редакса про impure. Сам стор работает при помощи грязных функций, с мутабельным изменением стейта.
Тоже самое можно сказать про интерпретатор Хаскеля и про его рантайм: они работают при помощи грязных функций с мутабельным изменением стейта. Означает ли это что Хаскель — язык, который не имеет ничего общего с ФП?

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

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

Впрочем, в redux-thunk и redux-saga эту проблему, кажется, решили.
> redux-saga эту проблему, кажется, решили.

Именно за счет функционального подхода в обход редакса, к слову.

> Вот с ней как раз проблема. Ее должна делать M, но как раз тот самый функциональный подход redux, в существование которого вы не верите, провоцирует переносить эту логику в C.

Как раз нет, провоцирует импьюрность редакса. В чисто функциональных сагах прекрасно все в модели остается.
Если бы редакс был импьюрным — проблемы бы не было, и сага была бы не нужна. Просто делаем запрос из редусера — и вперед! :-)
Просто если следовать той схеме с «классическим» пониманием MVC, то контроллер получается вообще встроен в браузер, соответственно, нет смысла его выделять и проецировать использование Redux и React на эту схему. На той схеме модель, фактически, содержит и бизнес-логику (поведение), и данные, и обёртки для изменения данных и генерацию уведомлений об их изменении — много всего. А Redux занимается именно работой с хранением, изменением и нотификацией об изменении данных (состояния) приложения внутри самого приложения, поэтому, если пытаться натянуть его на парадигму MVС получится, что он делает часть M.
Контроллер — это то что обрабатывает пользовательский ввод. В мире JS это обработчики событий.
Погодите — на схеме написано «взаимодействие с устройствами ввода». Обработчики событий этим не занимаются, не вычисляют, какой элемент кликнут, какой в фокусе и в какое поле записывать буквы с клавиатуры — им приходят уже готовые сообщения (события) с готовыми данными.
А вот как раз устройством ввода в мире HTML-песочницы и является браузер…
Но по схеме это контроллер решает, что делать с вью и с каким его куском, а в HTML мы наоборот вешаем обработчики на сами куски вью. Всё равно не складывается в MVC.
а в HTML мы наоборот вешаем обработчики на сами куски вью...

… которые (обработчики) потом сами решают что делать с вью и с каким его куском. Все так же, просто контроллер не собран в одном месте, а зачем-то размазан по разметке.


Кстати, забавно, но наиболее аккуратные реализации контроллера у меня получались на jquery, благодаря фиче Delegated events. С видом и моделью просто беда — а контроллеры идеальные :-)

> которые (обработчики) потом сами решают что делать с вью и с каким его куском.

Обработчик события — это метод модели, не контроллера (ну и не вида). Логика контроллера (по классике mvc) — это тот код, который вешает обработчик события на дом-узел (в случае использования спа-фреймворков его нет, он внутри фреймворка).

Што? Обработчик события — это функция, которой в качестве контекста (this-параметра) передается элемент DOM. А в качестве первого параметра — объект со свойствами target, currentTarget и, в jquery, originalTarget. И с методами preventDefault и stopPropagation...


Как его вообще можно отнести к модели?

> Што? Обработчик события — это функция, которой в качестве контекста (this-параметра) передается элемент DOM.

Не важно, что в нее передается, важно, что эта ф-я делает. Она (обычно) делает то, что делает модель, но не делает то, что делает контроллер.

Обработчик события преобразовывает события UI в сообщения модели, в вызов её методов. Именно то, что делает контроллер в MVC.

Обработчик события преобразовывает события UI в сообщения модели, в вызов её методов.
Ну, если вы его именно таким напишете — то да. Но это не означает, что в общем случае обработчики события == контроллер.

Если ваша модель принимает на вход DOM элементы или события, то в общем случае это не модель :)

А если обработчик вообще ни с какими «моделями» не работает, а просто модифицирует что-то в DOM'е или лезет на сервер — его всё равно можно назвать «контроллером»?
А чем еще его можно в таком случае назвать?
Ничем. Просто не нужно пытаться натянуть сову на глобус. HTML — это не MVC, это HTML. На нём можно (пытаться) реализовывать паттерн MVC, но сам по себе он — не MVC.
> Обработчик события преобразовывает события UI в сообщения модели, в вызов её методов.

Так обработчик события (в случае html) — и есть метод модели. В контроллере никаких «обработчиков» нет, там один событийный луп.
В 80-х вы бы считали в контроллере, что там находится (какая кнопка, меню, етц.) по координатам, в которых совершен клик, а потом бы вызывали на модели метод Button1Clicked();
В 2018 за вас все это (работу контроллера) делает браузер, контроллеру остается только указать, какой метод модели соответствует данному событию (привязать метод модели к конкретной дом-ноде).

Метод Button1Clicked не может быть в модели в принципе — потому что модель ничего не знает про кнопки.


Метод Button1Clicked может находиться в контроллере или в компоненте (который собирает в себе контроллер и вид).


А считать что там находится по координатам — это задача инфраструктуры.

> Метод Button1Clicked не может быть в модели в принципе — потому что модель ничего не знает про кнопки.

Это вы с чего взяли? Активная модель вообще методы из публичного апи вида вызывать может.

> А считать что там находится по координатам — это задача инфраструктуры.

Когда создавался mvc, подобной инфраструктуры не было. И именно такие вещи были обязанностями контроллера. Сейчас, в 2018, это действительно берет на себя инфраструктура, а контроллер оказывается, по сути, пустым.

> Метод Button1Clicked может находиться в контроллере

Не может его быть там. Контроллер не в курсе о кнопках.

> компоненте (который собирает в себе контроллер и вид)

В mvc никакого «компонента», в котором может что-то находиться (за пределами, с-но, m, v и с) просто нет.

Суть MVC прежде всего в полной изоляции модели от UI. Она ничего не должна знать о нём.

> Суть MVC прежде всего в полной изоляции модели от UI

Не так. Суть mvc — в изоляции контроллера от вида (входов черного ящика от его выходов) при помощи модели, которая выступает посредником между ними.

Никакой изоляции UI (вид+контроллер) от модели быть не может, потому что и вид и контроллер в MVC взаимодействуют с моделью напрямую

Модель посредником не выступает, она ничего не должна знать ни о контроллере, ни о виде.

> Модель посредником не выступает

В классическом mvc модель как раз посредник между ними. В этом смысл всего mvc.

> она ничего не должна знать ни о контроллере, ни о виде.

Если модель ничего не знает ни о виде, ни о контроллере, то вы просто не сможете связать вид с контроллером. Потому что у вас, получается, никто не знает ни о ком.
У вас очень странное понимание MVC. Не могли бы вы привести ссылки на источники?
Ну вообще в той же википедии приблизительно так и описано: Контроллер Модель изменяет, а Вид ее отображает. Но там не сказано, что модель знает о ком-то из них. В статье-источнике вполне привычная схема, где Модель ни о ком не знает и только посылает события:
Ну потому я и прошу источники, что Druu активно обсуждает какую-то «классическую» MVC, про которую, видимо, никто кроме него не знает.
Просто предполагаю я, что спор у вас больше терминологический, чем по сути.

Например, что означает «Модель знает о Виде». Когда Вид подписывается на события Модели — она о нем узнает или нет? Если Дрюю считает, что да, а вы, что нет, то суть спора только в разном понимании слова «знает».
Да нет, спор не терминологический. У разницы в терминах есть последствия: в зависимости от понимания MVC, метод Button1Clicked оказывается в разных местах.

Да, тут не поспоришь. Но, на счет Button1Clicked, мне кажется, тут срабатывает одна из двух основных проблем программирования — проблема именования. Такого метода впринципе не должно. То есть логика, конечно, на первый взгляд правильная есть клик кнопки — есть метод, который обрабатывает клик кнопки. Но мы вот сейчас передаем методы модели прям в onClick (у нас React+MobX). Например так (apiKeys — это модель):


<Tabs activeTabId={apiKeys.sortMethod} onChange={apiKeys.setSortMethod}>
    <Tab tabId={SortMethod.Newest}>newest</Tab>
    <Tab tabId={SortMethod.Oldest}>oldest</Tab>
    <Tab tabId={SortMethod.Az}>asc</Tab>
    <Tab tabId={SortMethod.Za}>desc</Tab>
</Tabs>

Или так:


<Select
    value={app.appVersion}
    onValueChange={app.setVersion}
>

Ну или банальное:


<button onClick={app.save} />

У нас, конечно, ближе к MVVM, но суть в том, что т.к. нету методов с названием ButtonClicked — у нас нету и вопросов, куда их ложить

Вот только VM в MVVM заменяет собой C. В модели не может быть свойства sortMethod, потому что в предметной области нет никакой сортировки.
Да, тут вы правы, у нас есть такие компромисы
> потому что в предметной области нет никакой сортировки.

Как это нет? Если, например, пользователь хочет отсортировать товар, то сортировка в предметной области вполне себе есть.

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


Да, иногда приходится вводить в модель методы типа "получить 100 записей, отсортированных по такому-то принципу, пропустив первые 1000", но это оптимизация

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

Ну какая разница, скажете вы, что это «пользователь» или «актор с ролью Х», хочет он уметь сортировать товар — будет сортировка в предметной области.

Актор совершает действия в бизнес-процессах, изменяет их состояние. Если сортировка часть бизнес-процесса, например, выставление приоритетов для задач — то это предметная область. Если сортировка лишь для удобства пользователей, то это логика отображения. В модель её вносят аргументированно лишь в случае протечки абстракций, например будучи уверенным, что за фасадом модели скрывается SQL-сервер, который заведомо более эффективно отсортирует 1000000 записей и выдаст 11-ю сотню из них, чем передача 1000000 записей в представление и сортировка их там.

Так как вы сформулируете задачу «обеспечить потенциальному клиенту возможность отсортировать товар», если понятия сортировки у вас в предметной области нет?

Ну то есть если у вас ее нет в предметной области — как такая функция вообще может появиться?

> который заведомо более эффективно отсортирует 1000000 записей и выдаст 11-ю сотню из них, чем передача 1000000 записей в представление и сортировка их там.

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

> то это логика отображения

А с каких пор логика отображения не относится к предметной области? Есть ведь задачи в которых, с-но, кроме логики отображения особо ничего и нет. У таких задач что, предметная область отсутствует? А что тогда диктует ту самую логику отображения, как не предметная область? Например в той же сортировке — по одним параметрам есть смысл сортировать, по другим нет. Какие параметры есть и по каким имеет смысл и как — именно от предметной области и зависит. Даже, например, выбор дефолтной сортировки — это предметная область.

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

> модель не должна «думать» об удобстве пользователя

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

Нет. Предметная область — это не удобство пользователя, а его задачи. Модель отдаёт список товаров и позволяет выбрать несколько из них — модель свою задачу решила. Удобство просмотра и выбора — задача UI.


Взять какие-то интернет- магазины — предметная область у них одна по сути, модель одна: товары, заказы, склады и т. п. А вот сортировки или фильтры — это различия в UI.

> Нет. Предметная область — это не удобство пользователя, а его задачи.

Так дело не в том чтобы задача решалась удобно. Дело в том, что сама задача существует лишь только за тем, чтобы обеспечить удобство пользователя. Если нет удобства пользователя — то и задачи нет. Сам интернет-магазин существует за тем, чтобы пользователю было _удобно_ заказать товар. Иначе он мог бы просто прийти в магазин ножками и купить что надо, и задача решена, без всякого интернет-магазина.

> Модель отдаёт список товаров и позволяет выбрать несколько из них — модель свою задачу решила.

А чего список? Почему бы не по одному и листать? А почему надо выводить именно данные Х? Почему не добавить к ним Y? Или не убрать Z? Удобство решения абсолютно всегда учитывается в задаче (потому что, как выше указано, любая задача состоит в том, чтобы обеспечить то или иное удобство пользователя, сделать более удобным то, что раньше было менее удобно). Отдельный вопрос — насколько полно, но в определенной мере — всегда.

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

> Задача пользователя купить товар в магазине.

Идет и покупает. Нету никакого интернет-магазина, нету никакого MVC, нету никакой модели. О чем речь?

Интернет-магазин не занимается решением задачи покупки. Это задача _уже решена_. Задача интернет-магазина — именно предоставить удобный способ, те самые конкурентные преимущества (или, по крайней мере, от конкурента не отстать).

Если же рассматривать задачу интернет-магазина именно как решение задачи покупки, то тогда оптимальный вариант (при существовании обычного магазина) — не делать интернет-магазин, понимаете?

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

> Интернет-магазин занимается решением задачи покупки.

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

Прибыли хочет как результата работы бизнес-процессов. Он, конечно и без них хочет, но он реалист.

Так и пользователь товар хочет получить как результат работы бизнес-процесса. Прекращайте-то словесную эквилибристику уже. Ну и, да, «получать прибыль» обычно задачи не ставится, по крайней мере, такая постановка считается плохой, негодной. Обычно речь о конкретике.
> У разницы в терминах есть последствия: в зависимости от понимания MVC, метод Button1Clicked оказывается в разных местах.

Ну собственно тут TheShock указал на суть. Важно, что именно в этом методе происходит.
> где Модель ни о ком не знает и только посылает события:

А как можно отсылать сообщения, ничего не зная? Сам факт отсылки сообщений предполагает знание апи того, кому эти сообщения отсылаются (не забываем, что отсылка сообщения = вызов метода).
А как можно отсылать сообщения, ничего не зная? [...] предполагает знание апи того, кому эти сообщения отсылаются

У модели есть EventEmitter, а у того — публичный интерфейс IListener. Те, кто хотят получать информацию о изменениях модели реализуют IListener. Модель не знает о View напрямую, лишь знает, что у нее есть какие-то IListener'ы, которые хотят знать, что модель оновилась.
> У модели есть EventEmitter, а у того — публичный интерфейс IListener.

Но модель не просто отсылает сообщения через eventemiter, она знает какие сообщения вид понимает, а какие — нет (с-но, что надо виду отсылать в каком случае). Она знает апи, по которому можно общаться с видом. Если апи вида поменяется (с нарушением обратной совместимости) — придется поменять и передаваемые ему из модели сообщения.

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

Это вид подписывается на интересующие его сообщения. И даже если нет явной инфраструктуры обмена сообщений, то это вид дергает методы модели типа onOrderCreated(callback), и модель дикутет сигнатуру этого колбэка, зная о его семанткие лишь то, что нужно передать информацию о созданном заказе. Для модели безразлично передавать его представлению, системе логирования, какой-то сторонней системе. Она лишь знает, что кто-то (кто она не знает) просит вызвать этот кэлбэк в случае создания заказа. С какой целью, с какой семантикой она не знает, а сигнатуру диктует сама.

> и модель дикутет сигнатуру этого колбэка, зная о его семанткие лишь то, что нужно передать информацию о созданном заказе.

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

Никто не может.

> У меня таких проблем не возникает.

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

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

> Модель сообщает своему абстрактному подписчику «создан заказ», что он будет делать с этой информацией ей вообще всё равно

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

Допустим, у вас вид реагировал на сообщения вида «создан заказ», потом что-то поменялось, и он на них не реагирует. Он отныне реагирует на сообщения вида «товар добавлен в корзину», которых раньше и не было. Если вы модель не исправите — ваше приложение перестало работать, поздравляю.

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

Бинго!

Но в нашем случае это не выполняется, приложение утрачивает работоспособность => модель зависит от вида.

Модель остаётся рабочей, только один вид не работает.

> Модель остаётся рабочей, только один вид не работает.

Вид как раз работает в соответствии со спецификацией. Не работает модель, которая не отправляет сообщение, которое в соответствии со спецификацией отправлять должна.

Это как раз зависимость вида от модели. Модель не делает что-то на что вид рассчитывает и вид не работает. Но модель работает, пока вид запрашивает от неё только то, что модель реализует. И если в спецификации появилось что-то новое и в модели оно реализовано, то для модели это абстрактное требование, а не зависимость от вида. Вид может изменяться как угодно, но модель меняется по требованию бизнес-аналитика, а не по факту изменения вида. Грубо, это разработчики модели могут оповестить всех разработччиков видов "мы меняем формат сообщений", а не разработчики вида сообщить "мы ожидаем новый формат сообщений".

> Модель не делает что-то на что вид рассчитывает и вид не работает.

Вид _работает_. Не работает модель.

> Но модель работает, пока вид запрашивает от неё только то, что модель реализует.

Это уже какой-то предел софистики. Любая система корректно работает, если от нее запрашивать только то, что она может корректно делать. Но на практике требования формируются не в виде «то, что можешь», а «то, что нужно».

> Вид может изменяться как угодно, но модель меняется по требованию бизнес-аналитика, а не по факту изменения вида.

Бизнес-аналитик формирует свои требования, как требования к интерфейсу взаимодействия(общего характера, как в примере выше). О модели бизнес-аналитик ничего не знает. Сам факт наличия модели — деталь реализации. Уже разработчик смотрит на требования к виду и делает вывод, что чтобы обеспечить возможность их выполнения в рамках имеющейся системы, требуется изменить модель (которой, еще раз, может вообще не быть. Сам факт ее существования — деталь реализации, никто кроме разработчиков не знает, что она в принципе есть, бизнес-аналитик не знает, что она есть!)
Вид _работает_. Не работает модель.

Но вид не может работать, если не работает модель. Он ведь на нее завязан! Если он добавил что-то, а модель это не поддерживает, значит вид не работает
> Но вид не может работать, если не работает модель.

Вы сейчас уходите в сторону обсуждения того, что значит «работает» и «не работает».

Давайте определимся — «не работает» в системе тот модуль (модули), который необходимо исправить для того, чтобы система в целом заработала согласно спецификации.
В данном случае исправить требуется модель.

Если вы хотите как-то иначе определить «не работает» — прошу, рассмотрим ваш вариант.

Я рассматривал изначально ситуацию, в которой вью стал ожидать от модели что-то, чего нет в спецификации модели. В этой ситуации вью не работает. Потому что вью зависит от модели, а не наоборот. Если с моделью взаимодействуют по её спецификации и она выдаёт ожидаемые результаты, то она работает.

> Потому что вью зависит от модели, а не наоборот.

Они зависят друг от друга. Не бывает такого, что X зависит от Y и при этом Y не зависит от X. Зависимость всегда двусторонняя.

> Я рассматривал изначально ситуацию, в которой вью стал ожидать от модели что-то, чего нет в спецификации модели.

А я рассматривал ситуацию, при которой вью ожидает от модели то, что есть в спецификации модели.

Бывает. Простой пример — ОС не зависит от прикладного софта.


Если что-то есть в спецификации модели, а она это не реализует, то это баг, а не зависимость.

Зависимость — это когда наличие Х в спецификации вида требует наличия Y в спецификации модели. Такое бывает.

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

Это зависимость требований к модулям, вернее декомпозиция требований к системе на требования к модулям, а не зависимость модулей.


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

> а не зависимость модулей

Если для изменения модуля А в общем случае требуется менять модуль Б — то модуль Б зависит от модуля А. Для меня это определение понятия «зависимость между модулями». Как определяете это понятие вы?

Если изменения модуля А требуют в общем случае для сохранения функциональности модуля Б менять последний, то модуль Б зависит от модуля А. Ключевое различие в "сохранение функциональности".

> Если изменения модуля А требуют в общем случае для сохранения функциональности модуля Б

Но у нас функциональность модуля Б не сохраняется. Он оказывается сломан.

> Они не отвечают, они лишь представляют и управляют. Если чего-то в модели нет, то нечего представлять и ничем управлять

Именно отвечают. Еще раз — модели может просто _не быть_. А приложение будет работать и соответствовать спецификации. Еще раз -на приемке никто не проверяет модель. Проверяется только интерфейс. Ваша модель может сколь угодно сильно отличаться от предметной области — все равно никто этого не заметит. Даже ее отсутствия никто не заметит.

> Не обязаны термины, а тем более правила, быть описаны в UI.

Ну попробуйте описать их нен в UI. Приведите пример.

> Термины может выдавать модель по какому-то протоколу типа GraphQL или HATEOAS

Но требование «выдавать информацию» — это требование к виду. Информацию выдает вид.

> По определению MVC.

Нету там такого в определении. В определении MVC вообще нету бизнес-процессов, модели предметной области и прочего.

> Акторы взаимодействуют друг с другом через модель.

Нет, они взаимодействуют друг с другом через интерфейс. Еще раз — модели вообще может не быть. Как они тогда по-вашему взаимодействуют? Откуда вообще актор знает, что есть какая-то «модель» если никак не может ее пощупать и пронаблюдать?

> Могу.

Эм… прошу прощения, как? Если вы преедали информацию в модель — значит вы воспользовались контроллером. Получили информацию от модели — это был вид. Как без них? Можно пример?

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

Может. И?
Но у нас функциональность модуля Б не сохраняется. Он оказывается сломан.

Модуль Б работал два года, его функциональность задокументирована, зафиксирована, мониторится, никаких нареканий. Модуль А изменяет своё взаимодействие с модулем Б на какой-то сценарий, который никому кроме авторов модуля А неизвестен, модуль А ломается, модуль Б работает по своей спецификации дальше. Модуль А зависит от модуля Б, а не наоборот.


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


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

Давайте определимся, мы о MVC говорим или нет? Если о MVC, то модель должна быть и за ней должна прятаться вся логика, кроме логики UI. Если не о MVC, то модель (бизнес-модель) может быть размазана по всему приложению, но быть она должна, чтобы приложению выполняло свою бизнес-функцию.


Еще раз -на приемке никто не проверяет модель. Проверяется только интерфейс.

Сильно зависит от согласованной процедуры приемки. Кроме того, интерфейсов к модели может быть много и далеко не все они могут быть построены на базе MVC, тогда в ходе приёмки может выявиться, что модель работает, а конкретная пара VC — нет. Более того, M и VC могут разрабатывать разные команды в разное время и сдаваться по отдельности в принципе.


Ну попробуйте описать их нен в UI. Приведите пример.

Метаданные объектов модели, включая название, описание, связи и т. п. полей. Грубый пример: какой нибудь PhpMyAdmin используемый как UI к модели в базе — он ничего не знает о бизнес-логике в базе, но всю информацию показывает, включая описания в виде комментариев.


Но требование «выдавать информацию» — это требование к виду. Информацию выдает вид.

Это требование к модели. Вид информацию показывает, но выдаёт её модель.


Нету там такого в определении. В определении MVC вообще нету бизнес-процессов, модели предметной области и прочего.

Finally, the model manages the behavior and data of the application domain, responds to requests for information about its state (usually from the view), and responds to instructions to change state (usually from the controller).


Нет, они взаимодействуют друг с другом через интерфейс. Еще раз — модели вообще может не быть. Как они тогда по-вашему взаимодействуют? Откуда вообще актор знает, что есть какая-то «модель» если никак не может ее пощупать и пронаблюдать?

Актор взаимодействует с интерфейсом модели, а не друг с другом (в рамках моделируемого процесса). VC — это пользовательский интерфейс модели для её взаимодействия с человеком в роли того или иного актора. C транслирует события UI в команды модели, V — отображает актуальный срез состояния модели.


Эм… прошу прощения, как? Если вы преедали информацию в модель — значит вы воспользовались контроллером. Получили информацию от модели — это был вид. Как без них? Можно пример?

Не обязательно контроллером. Каким-то другим клиентом одели, не на базе MVC. Это и ответ на "Может. И?"

> Модуль А изменяет своё взаимодействие с модулем Б на какой-то сценарий, который никому кроме авторов модуля А неизвестен

Да как же неизвестен? Известен.

> модуль А ломается, модуль Б работает по своей спецификации дальше.

Так:

Давайте определимся — «не работает» в системе тот модуль (модули), который необходимо исправить для того, чтобы система в целом заработала согласно спецификации.

Согласно такому определению, не работает модуль Б. Вы хотите предложить другое определение понятию «не работает»?

> Давайте определимся, мы о MVC говорим или нет? Если о MVC, то модель должна быть и за ней должна прятаться вся логика, кроме логики UI.

Все верно. Только нигде ничего про бизнес-процессы и модель предметной области там нет. Логика приложения (и, с-но, MVC модель) может быть сформулирована в терминах сколь угодно далеких от терминов предметной области. Вы упорно путаете две разные модели. Модель mvc и модель предметной области — разные вещи. Первая не обязана выражать вторую.

> Сильно зависит от согласованной процедуры приемки.

Не зависит. Проверить на приемке модель просто нет физической возможности. Можно проверить только интерфейс.

> тогда в ходе приёмки может выявиться, что модель работает

Единственный способ убедиться в работоспособности модели- протестировать ее на паре v/c. Что докажет работоспособность конкретных v/c, а не модели, на самом-то деле.

> Метаданные объектов модели, включая название, описание, связи и т. п. полей. Грубый пример: какой нибудь PhpMyAdmin используемый как UI к модели в базе — он ничего не знает о бизнес-логике в базе, но всю информацию показывает, включая описания в виде комментариев.

Ну так phpMyAdmin это вью (вы же сами это написали) и его функционал описывается в терминах интерфейса.

> Это требование к модели. Вид информацию показывает, но выдаёт её модель.

А может — и не выдает. Вид, между тем, будет информацию показывать. В итоге требование выполнено, а значит — это требование вида.

> Finally, the model manages the behavior and data of the application domain, responds to requests for information about its state (usually from the view), and responds to instructions to change state (usually from the controller).

Я специально выделил. Чтобы было понятно — булево значение для менюшки, которая хранит стейт (раскрыта менюшка или нет) входит в application domain. То, что оно не входит в модель предметной области (обычно) думаю, понятно.

> Не обязательно контроллером. Каким-то другим клиентом одели, не на базе MVC.

Тогда это и не MVC-модель, если уже не на базе MVC. А mvc-модели иначе как через контроллер ничего не передашь.
Давайте определимся — «не работает» в системе тот модуль (модули), который необходимо исправить для того, чтобы система в целом заработала согласно спецификации.
Согласно такому определению, не работает модуль Б. Вы хотите предложить другое определение понятию «не работает»?

"Не работает" в системе тот модуль, который не соответствует спецификации на модуль. Есть система из модулей А, Б, В, Г. Есть общая спецификация на систему, есть спецификации на модули, образованные в основном декомпозицией спецификации на систему и архитектурными решениями. В какой-то момент система работает, соответствует спецификации. И каждый модуль в ней работает, соответствует спецификации. Потом приходит новое требование к системе, при анализе оно вырождается в требование к модулю А. Реализуется, модуль А работает (при модульном тестировании) согласно новой спецификации, все остальные тоже работают, но при тестировании системы в целом выявляется, что система новой спецификации не соответствует, и вообще выдаёт ошибку типа "Метод МодульБ.операция() не найден в МодульА строка 7" ещё на сталии сборки. Анализ проблемы показывает, что разработчики модуля А почему-то решили, что вместе со спецификацией модуля А должна была измениться соответствующе и спецификации модуля Б, но разработчики модуля Б даже если знали о новых требованиях к системе, то решили, что их они не касаются, что функциональность будет реализована или в модуле А целиком, или частично в нём, а частично в модулях В и Г, или будет созданы модули Д и Е. Спецификации модуля Б не изменились. Вывод: модуль Б работает, как и работал, а модуль А не работает по причине обращения к модулю Б способами, не предусмотренными спецификацией последнего, по причине создания очередной зависимости модуля А, но в данной ситуации неразрешенной.


Только нигде ничего про бизнес-процессы и модель предметной области там нет. Логика приложения (и, с-но, MVC модель) может быть сформулирована в терминах сколь угодно далеких от терминов предметной области. Вы упорно путаете две разные модели. Модель mvc и модель предметной области — разные вещи. Первая не обязана выражать вторую.

Как по мне, то приложение, созданное для решения задач в какой-то предметной области, обязано работать в терминах этой предметной области. А в случае MVC-приложения эта обязанность реализуется прежде всего в модели, прежде всего она несёт семантическую нагрузку представления предметной области в приложении. Не согласны?


Не зависит. Проверить на приемке модель просто нет физической возможности. Можно проверить только интерфейс.

Не перегибайте. Есть множество способов проверить модуль/слой системы, не пользуясь одним из его пользовательских интерфейсов, одним из его клиентов.


Единственный способ убедиться в работоспособности модели- протестировать ее на паре v/c. Что докажет работоспособность конкретных v/c, а не модели, на самом-то деле.

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


Ну так phpMyAdmin это вью (вы же сами это написали) и его функционал описывается в терминах интерфейса.

Его функциональность описывается в терминах предметной области "СУБД MySQL", но при использовании его в качестве UI к конкретной БД, надлежащим образом спроектированной и заполненной по какой-то прикладной предметной области, это UI для пользователя "магическим образом" начинает работать в терминах предметной области, пользователь видит на экране типы типа User, UserGroup, Order, OrderItem и т. п., их экземпляры, их свойства и т. п.


А может — и не выдает. Вид, между тем, будет информацию показывать. В итоге требование выполнено, а значит — это требование вида.

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


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

Перевёл для ясности. Как по мне то, состояние UI не входит обычно в состояние application domain. У вида и контроллера могут быть свои состояния, никакого отношения к application domain не имеющие. И это прежде всего именно такие флаги, типа раскрыта менюшка или нет. Могут быть в application domain данные, которые транслируются в такие флаги, например код во вью типа this.isSelectAgentMenuOpen = model.currentUser.currentWorkflow.state === AbstractWorkflow::SELECT_AGENT_STEP, но в общем случае состояние приложения в рамках MVC состоит из состояния UI + состояния предметной области приложения.


Тогда это и не MVC-модель, если уже не на базе MVC. А mvc-модели иначе как через контроллер ничего не передашь.

Никто не запрещает обращаться к MVC-модели не из VC. MVC — это лишь один из паттернов создания UI, наиболее применимый в области оконных GUI. Никто не запрещает иметь в приложении, в котором для реализации одного из UI есть MVC, иметь другие UI, не говоря об API. MVC не подразумевает, что изменения в модели могут происходить исключительно из контроллера, а модель рапортовать об изменениях исключительно виду. И уж явно подразумевается, что модель чаще всего имеет несколько видов и контроллеров, а процессы дерганья конкретным контроллером мутатора модели и перерисовки связанного с этим контроллером вида ортогональны: контроллер дернул мутатор и надо перерисовать вид, контроллер дернул мутатор и не надо перерисовывать вид, контролер не дергал мутатор, но всё равно надо вид перерисовать.

> «Не работает» в системе тот модуль, который не соответствует спецификации на модуль.

Ну тогда все элементарно — не работает модель, т.к. именно она спецификации не соответствует. Остальное соответствует.

> Как по мне, то приложение, созданное для решения задач в какой-то предметной области, обязано работать в терминах этой предметной области.

Это бы, конечно, хорошо, но, нет, не обязана. И это часто не так.

> Есть множество способов проверить модуль/слой системы, не пользуясь одним из его пользовательских интерфейсов, одним из его клиентов.

Только написать другой клиент/интерфейс.

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

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

> Перевёл для ясности. Как по мне то, состояние UI не входит обычно в состояние application domain

А куда же оно входит? Это то состояние, с которым непосредственно взаимодействует пользователь.

> Никто не запрещает обращаться к MVC-модели не из VC.

Но это не то, что мы обсуждаем.
Ну тогда все элементарно — не работает модель, т.к. именно она спецификации не соответствует. Остальное соответствует.

Модель соотвествует спецификации модели. Что кто-то новые требования к системе в целом не отразил в спецификации модели, а отразил только в спецификации вида — не проблема модели. А если отразили и там, и там, то это зависимость спецификации модулей от спецификации системы, а не модулей друг от друга.


Только написать другой клиент/интерфейс.

Именно, но он не обязан быть пользовательским (например, автотесты), а даже если пользовательский, то не обязан быть реализован по MVC. Проверка модели заключается в проверке того, что модель исполняет свой контракт, которым как клиенты пользуются V и C. Проверять на реальных VC нет необходимости, а нередко и нет возможности, когда спецификация модели более обширна, чем та её часть, которыми пользуются VC.


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

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


А куда же оно входит? Это то состояние, с которым непосредственно взаимодействует пользователь.

В состояние UI вестимо. Где-то между видом и контроллером размазано.

> Что кто-то новые требования к системе в целом не отразил в спецификации модели, а отразил только в спецификации вида — не проблема модели.

Все отражено.

> А если отразили и там, и там, то это зависимость спецификации модулей от спецификации системы, а не модулей друг от друга.

Да какая разница? Еще раз, у вас модель не соответствует спецификации, значит она, по определению, не работает. Если модуль б из-за изменений спецификации модуля А (каковое, конечно, вызывает изменение спецификации модуля Б) оказывается сломан, то, по определению, модуль Б зависит от модуля А.

> Модель не зависит от вида.

Ну как же не зависит, если требования к виду порождают требования к модели, а изменения вида — порождают изменения модели? Что тогда для вас «зависит»?

> В состояние UI вестимо.

Ну а состояние UI входит в application domain.

Бизнес-аналитик не выдаёт требования к интерфейсу в общем случае, а вот к модели требования выдавать его прямая и основная обязанность. Его задача генерировать модели бизнес-процессов. И в информационной системе, моделирующей эти бизнес-процессы эта модель всегда есть. Она может быть выделена в отдельный изолированный, независящий от UI слой, а может быть размазана по всему коду, но она всегда есть. Это не описание интерфейсов, а описание реакций на события, полученные через интерфейсы или ещё откуда.

> Бизнес-аналитик не выдаёт требования к интерфейсу в общем случае, а вот к модели

Он не знает о существовании модели, и потому не может физически выдавать о ней требования!

Требования же к интерфейсу он выдает де-факто. «просмотреть товар, положить в корзину» — классический пример требований к интерфейсу (и, да, возможно это физическая корзина, в которую чисто физически пользователь кладет товар с физической полки).

> И в информационной системе

Ее может и нет.

> Это не описание интерфейсов, а описание реакций на события, полученные через интерфейсы или ещё откуда.

Бизнес-аналитик описывает действия акторов, то есть взаимодействия с интерфейсом. Ничего иного он описать _физически не в состоянии_. Он не знает ни о чем кроме акторов и интерфейса. Реакцию _чего_ в вашей постановке описывает бизнес-аналитик, если ничего больше для него нет?
Он не знает о существовании модели, и потому не может физически выдавать о ней требования!

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


Бизнес-аналитик описывает действия акторов, то есть взаимодействия с интерфейсом.

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

> Он формирует модель бизнес-процесса, которая является формулировкой требований к модели в рамках MVC.

А, я понял в чем проблема. Вас смутило, что и в «модель mvc» и в «модель предметной области» и там и там «модель», и вы подумали, что они имеют что-то общее. Это не так, конечно же, за соответствие предметной области в mvc отвечает вид с контроллером. Именно по виду и контроллеру, а не по mvc-модели, происходит проверка соответствия системы требованиям бизнес-аналитика. Модель никто не проверяет, кроме разработчиков она вообще никому не нужна (ее свойства и сам факт ее существования — деталь реализации, бизнес-процессы не предъявляет требований к деталям реализации, большей частью и ничего не знают о mvc), она может быть какой угодно и как угодно сильно отличаться от модели предметной области. Естественно, хорошо, когда m соответствует модели предметной области, но часто это не так. Например, если у вас старое legacy — там в m обычно куча костылей и подпорок, т.к. требования меняются, функционал допиливается, переработать модель полноценно времени нет, ограничиваются костылями. При этом v/c предметной области, конечно, будут соответствовать, как это и должно быть (по ним же приемка проводится).

> Не действия, а результаты действий, не взаимодействие с интерфейсом, а результаты взаимодействия.

И действия и взаимодействия и результаты. Только в бизнес процессах действует контроллер и вид, модели (mvcшной) нету там, она эффективно сокрыта.

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

> Вид с контроллером не отвечают за модель предметной области в рамках MVC

Именно они и отвечают, потому что именно по ним потом будет проводиться приемка. Соответствует ли ваша система требованиям. По модели она проводиться не будет. На нее кроме разработчиков все хотели класть, потому что про нее никто кроме них и не знают. А вот про v/c — знают, потому что непосредственно с ними взаимодействуют.

> Сама же предметная область, её термины и правила описаны в модели.

Они _могут_ быть там описаны (и это будет хорошо). А могут и не быть (и это будет плохо, но не катастрофа). С другой стороны, в v/c они быть описаны _обязаны_.

Они не отвечают, они лишь представляют и управляют. Если чего-то в модели нет, то нечего представлять и ничем управлять (очковтирание, когда разработчики UI замокали то, чего в модели нет, чтобы показать картинку, в расчёт не берём).


Не обязаны термины, а тем более правила, быть описаны в UI. Термины может выдавать модель по какому-то протоколу типа GraphQL или HATEOAS, а UI их использовать без всякого понимания семантики.

О каком таком «нашем случае» вы говорите?
> О каком таком «нашем случае» вы говорите?

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

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

Так, очень часто на стороне UI делают периодический опрос модели по таймеру. И иногда это даже бывает правильным решением!

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

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

И вы получите неработающее приложение (приложение, которое не компилируется, очевидным образом, не работает).

> Так, очень часто на стороне UI делают периодический опрос модели по таймеру.

Да в реальности вообще много чего делает. Собственно, запрос данных от модели видом (вместо подписки) один из двух вариантов MVC.

> Наконец, последний способ — просто заранее добавить в модель всевозможные события на все случаи жизни.

Не сможете. Точнее, единственный способ такое реализовать — просто эмитить _любой_ апдейт модели со всеми вытекающими.

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

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

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

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

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


А вот сделать вид чтобы он мог работать с любой моделью — в общем случае не получится.


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

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

Можно, но какая разница?

> А вот сделать вид чтобы он мог работать с любой моделью — в общем случае не получится.

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

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

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

Так что если у вас есть задача, то можете смело объявить, что у вас есть работающая модель для этой задачи. Только без контроллера и вида. Все равно никто никак не проверит.
Почему не поверит? Я вполне себе писал в ВарГейминге модель для игры, покрытую тестами, которую потом должны были натягивать на вид. И все поверили, закрыли задачу на мне и повесили задачу на ребят по натягиванию вида.
которую потом должны были натягивать на вид
На которую должны были натягивать вид, конечно
> Почему не поверит?

Не п_р_оверит. Ну потому что если могут проверить — значит вид/контроллер есть :)
А пока нету — проверить не смогут.
То есть, тесты, по вашему — Вид?
MVC — это не более чем модель черного ящика. Контроллер — название для input, вид — название для output, модель — ну, сам ящик. Если вы посмотрели, что вернула ф-я, то вы посмотрели output. Это вид.

То есть, если рассматривать как MVC систему модель+тесты+тестирующая система, то там будет и контроллер и вид.
Существует такая вещь как спецификация. Обособленная часть программы работает если ее поведение соответствует спецификации.

Спецификация может существовать в голове у программиста, на бумаге или в форме модульных тестов.
> Обособленная часть программы работает если ее поведение соответствует спецификации.

Ну так вы не можете проверить соответствие спецификации если вида/контроллера нет. Например, написали вы ф-ю — но не можете ее запустить. Не можете протестировать. Т.к. если вы передали в нее данные — вы воспользовались контроллером, а если посмотрели что она возвращает — видом.

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

Как минимум, есть формальные методы анализа кода на соответствие формальным спецификациям. Дорогие очень, но есть.

Бизнес-логика продолжает работать, если вид не обрабатывает сообщение "заказ создан", то значит в данном контексте даному типу пользователей это никак не нужно отображать. Модель это вообще не интересует, разработчика "бэкенда" это не интересует.


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


Модель — ядро системы, её заказчики — бизнес-аналитики, специалисты по бизнес-процессам.


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

> Бизнес-логика продолжает работать

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

> если вид не обрабатывает сообщение «заказ создан», то значит в данном контексте данному типу пользователей это никак не нужно отображать.

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

> разработчика «бэкенда» это не интересует.

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

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

Все верно. То есть, еще раз, пока вы не исправите модель (не подгоните ее под ожидания вида) ваше приложение — не работает. Следовательно, модель зависит от вида.

> Вью и контроллеры — лишь адаптеры к ней для пользователя

Ну вот в этом ваша проблема. Именно вид и контроллер первичны, потому что именно с ними взаимодействует пользователь. С моделью он не взаимодействует, ее он не видит, на нее ему плевать. Модель нужна лишь за тем, чтобы обеспечивать функционирование контроллера/вида (чтобы пользователь получал от них требуемую реакцию). Сама по себе она бесполезна и никаких функций не выполняет.

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

Модель продолжает работать, пользователь может заказать товар через другой вид или через командную строку, например.


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

Какие претензии? Того же уровня, что "почему не работает запрос "выбрать * из таблица" в sql сервере?"? Модель предоставляет свой апи для клиентов — контроллера и вида. Нужно их разработчикам что-то чего нет — просят добавить, описывают логику, получают согласие, оценку сроков и т. д. Но никак не начинают разработку, рассчитывая что оно там появится лишь потому они подписались на какое-то событие, о котором никто кроме них не знает.


Именно вид и контроллер первичны, потому что именно с ними взаимодействует пользователь. С моделью он не взаимодействует, ее он не видит, на нее ему плевать. Модель нужна лишь за тем, чтобы обеспечивать функционирование контроллера/вида (чтобы пользователь получал от них требуемую реакцию). Сама по себе она бесполезна.

Вот в этом ваша проблема. Пользователю интересно прежде всего взаимодействие с моделью. Ему интересно получить товар, а не увидеть сообщение "товар доставлен" в виде. Вид и контроллер лишь обеспечивают удобное взаимодействие с моделью, не более. Грубо, без них пользователь может заказать товар, просмотрев списиок товаров по GET запросу отправив заказ POST запрос curl'ом, но это неудобно.


Модель проектируется обычно без учёта логики работы вида/контроллера, их может вообще не оказаться в приложении, а модель будет. Спроектированная по бизнес-процессам, например в BPM нотации. Она может адаптироваться потом под запросы разработчиков UI, но оперировать будет не понятиями UI, а понятиями предметной области, тем, что реально пользователю нужно: товарами, заказами, корзинами и т. п., лишь для оптимизации фич типа сортировки эти фичи будут добавлены в модель, а не останутся во вью.

> Модель продолжает работать, пользователь может заказать товар через другой вид или через командную строку, например.

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

> Какие претензии?

В том, что модель не соответствует спецификации. При добавлении товара в корзину, она должна оповестить вид, а она — не оповещает.

> Модель предоставляет свой апи для клиентов — контроллера и вида.

Верно, она предоставляет апи, который _требуется_ контроллерам и видам. Если контроллеру и виду надо решить какую-то задачу, которая не решается в рамках существующего апи (или решается неудобно), то ставится задача доработки модели под нужды контроллера/вида. Таким образом, модель зависит от контроллера и вида (от того, какое апи они требуют для того, чтобы обеспечивать функционал).

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

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

> Вот в этом ваша проблема. Пользователю интересно прежде всего взаимодействие с моделью.

пользователю неинтересно взаимодействие с моделью, потому что он с ней не взаимодействует и взаимодействовать не может. Он взаимодействует с интерфейсом. Задачи всегда формулируются в терминологии интерфейса, т.к. описывают действия акторов, а акторы работают с интерфейсом (не с моделью).
Давайте на примере: «клиент выбирает товары из списка, добавляет в корзину, проверяет содержимое корзины, отправляет заказ, получает подтверждение, менеджер получает информацию о заказе».

Это все — описание взаимодействия с интерфейсом, требований к интерфейсу. От интерфейса требуется предоставить возможность просмотреть товары, добавить в корзину, просмотреть корзину и отправить заказ, убедившись в его отправке, просмотреть информацию о заказе. Да, здесь нет конкретики (не сказано, какой кнопкой отправляет клиент заказ (и вообще кнопкой ли), в каком виде представлен список товаров, в каком виде менеджер получает информацию о заказе (список на сайте, рассылка, сообщение от голосового бота)) -требования формируются в общем, но это именно интерфейсные требования. Они описывают какие возможности взаимодействия пользователя с вашей системой следует предоставить. Модели тут нигде нет. Сам факт наличия модели — лишь деталь реализации. И смысл модели тут — обеспечить возможность интерфейсу реализовать указанные фичи (вывод товаров, отправка заказа, получение инфы о заказе).
Если модель должна позволять заказывать товар двумя способами, но предоставляет только один способ — то она не работает.

Модель позволяет заказать товар одним способом: вызывом метода order(goods). Какие UI есть, дергающие этот метод, модели всё равно.


В том, что модель не соответствует спецификации. При добавлении товара в корзину, она должна оповестить вид, а она — не оповещает.

Кому должна? Бизнес-аналитики изложил это в требованиях к моделированию бизнес-процесса?


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

Нет, она предоставляет api, который либо поставляется как есть, либо согласован всеми заинтересованными сторонами. Прежде всего, владельцем бизнес-процесса. У модели часто много разных UI и практически всегда много контроллеров/видов. Изменения в API ради одного вида, обычно никто просто по просьбе делать не будет.


Это все — описание взаимодействия с интерфейсом, требований к интерфейсу.

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

> Модель позволяет заказать товар одним способом: вызывом метода order(goods)

Это кто сказал? Если в требованиях нужно уметь заказывать каким-то другим способом — значит должен быть и другой. Давайте, чтобы было проще, говорить не о двух вариантах одной ф-и, а о разных. Модель должна предоставить возможность положить товар в корзину и заказать. Если одно реализовано, а другое нет — модель не работает.

> Кому должна? Бизнес-аналитики изложил это в требованиях к моделированию бизнес-процесса?

Конечно.

> Нет, она предоставляет api, который либо поставляется как есть

Конечно, нет. Она предоставляет тот апи, который требуется для выполнения конкретных задач.

> Изменения в API ради одного вида, обычно никто просто по просьбе делать не будет.

Если это необходимо для реализации определенного функционала — будут, как же иначе?

> Нет. Это описание бизнес-процессов, какой конкретно интерфейс (сайт открыл, ножками пришел, звонок сделал)

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

> приложения типа интеренте-магазинов лишь изменили интерфейсы к ней.

Так я вам то же самое таллдычу. Бизнес-аналитик формирует требования к интерфейсу. ВОЗМОЖНО, в качестве интерфейса у вас будет интернет-магазин (возможно и нет). но это не меняет того факта, что требования сформулированы как требования к интерфейсу, то есть (если говорить об интернет-магазине на MVC архитектуре) — к виду/контроллеру. не к модели (которая, еще раз, лишь деталь реализации и не факт что есть. нету у вас интернет магазина, с-но и MVC нету и модели тогда — тоже нету, внезапно!).
Конечно

Вот. Модель зависит не от вида, а от бизнес-аналитика, от бизнес-процессов.


Конечно, нет. Она предоставляет тот апи, который требуется для выполнения конкретных задач.

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


Если это необходимо для реализации определенного функционала — будут, как же иначе?

"исходные данные мы вам предоставляем"


Бизнес-аналитик формирует требования к интерфейсу.

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

> Вот. Модель зависит не от вида, а от бизнес-аналитика, от бизнес-процессов.

Вид зависит от бизнес-аналитика (бизнес аналитик описывает вид), модель — от того, что описал бизнес-аналитик. А раз бизнес-аналитик описывает вид — модель зависит от вида.

> Если он решает, что нет смысла нагружать модель сортировкой или фильтрацией

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

> Бизнес-аналитик формулирует описание бизнес-процесса.

И формулирует он его в терминах взаимодействия акторов с интерфейсом.

> Касательно пользовательских интерфейсов он описывает какие события они должны генерировать, не более.

То есть, формулирует функциональные требования к интерфейсу. Это задача бизнес-аналитика. Формулировать требования к интерфейсу.

Бизнес-аналитик не описывает виды в общем случае, он про них не знает :) Он описывает модели в терминах взаимодействия акторов друг с другом, но не их пользовательские интерфейсы. Описывает сообщения, который один актор может послать другому, и описывает как получатель может на это отреагировать. Механизм отправки сообщений он не описывает, подразумевая, что разработчики договорятся об интерфейсе.

> Бизнес-аналитик не описывает виды в общем случае, он про них не знает :)

Конечно, не знает, но те требования которые он описывает _в случае если мы пишем ИС на основе MVC-архитектуры_ необходимо должны быть реализованы в виде/контроллере. Они _могут быть_ (и это желательно) реализованы в модели, но это уже не является необходимым.

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

Ну да, и в итоге это накладывает условия на ваш вид и контроллер (они должны предоставить возможность описанной передачи сообщений в том или ином виде). Но не накладывает (напрямую) никаких условий на m. Возможно, реализация данного функционала в v/c потребует наложить аналогичные условия на m. Но может — и не потребует. Может, других каких-то потребует :)
Это уже решит разработчик, исходя из конкретного контекста, что там за система, что уже реализовано, и т.д.
Что там он в модели наворотит в итоге проверяться на приемке не будет. Может, у него там зигохзистоморфические препроморфизмы и стрелки Клейсли.

Прежде всего они должны быть реализованы в модели. Модель в MVC — это единственный способ взаимодействия акторов друг с другом. Вид и контроллер — пользовательские интерфейсы к модели, без модели они бесполезны. А у модели могут быть и другие интерфейсы, не относящиеся к парадигме MVC.


Проверяться будет. Каким бы удобным интерфейс магазина не был, его не примут, если менеджер не видит заказа, который создал (если верить пользовательскому UI) пользователь.

> Прежде всего они должны быть реализованы в модели.

Нет, не должны. Зачем?

> Модель в MVC — это единственный способ взаимодействия акторов друг с другом.

Акторы не взаимодействуют с моделью. Они взаимодействуют с видом и контроллером.

> Вид и контроллер — пользовательские интерфейсы к модели, без модели они бесполезны.

Ну как сказать. Можно насовать всю логику в v/c и оставить сверхтонкую модель, которая будет делать примерно ничего. И ваше приложение — будет рабочее приложение, соответствующее описанию требуемых бизнес-процессов, в котором полезные v/c и бесполезная модель.
С другой стороны — модель сама по себе, без v/c, никаких функций выполнять не может. Потому что вы не сможете ни передать ей данные, ни получить их от нее.

> А у модели могут быть и другие интерфейсы, не относящиеся к парадигме MVC.

Да ради бога. Мы обсуждаем MVC. В каких-то других архитектурах что-то может быть иначе.
Нет, не должны. Зачем?

По определению MVC.


Акторы не взаимодействуют с моделью. Они взаимодействуют с видом и контроллером.

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


Ну как сказать. Можно насовать всю логику в v/c и оставить сверхтонкую модель, которая будет делать примерно ничего.

Это будет не MVC


С другой стороны — модель сама по себе, без v/c, никаких функций выполнять не может. Потому что вы не сможете ни передать ей данные, ни получить их от нее.

Могу. Например, писать программы на каждое нужное мне действие. Грубо, аналог SQL запросов к SQL серверу, где SQL сервер — модель, но чтобы получить обновление "вида" мне нужно сделать запрос явно. В MVC же ключевой фактор — вид изменяется автоматически.


Да ради бога. Мы обсуждаем MVC. В каких-то других архитектурах что-то может быть иначе.

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

UFO just landed and posted this here
Если модель ничего не знает ни о виде, ни о контроллере, то вы просто не сможете связать вид с контроллером. Потому что у вас, получается, никто не знает ни о ком.

Контроллер знает о Модели и Вид знает о Модели. Разве этого недостаточно?
> Но по схеме это контроллер решает, что делать с вью и с каким его куском, а в HTML мы наоборот вешаем обработчики на сами куски вью. Всё равно не складывается в MVC.

Я ниже об этом писал. В 80-х у вас устройства ввода/вывода были разделены, сейчас же интерфейс, который рисует браузер — это одновременно и view, и controller. По-этому та логика, что раньше разделялась — сейчас логичным образом в одном месте (и тривиальна, по сути и логики никакой нет). За исключением тех редких случаев, когда логику событий/рендера все-таки определять нужно.
Так я, собственно, про это и писал — что HTML на MVC «в классическом понимании» не ложится.

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

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

И все это вместе называется объектом в ООП. Объекты они же про менеджмент своего локального состояния, так с чем же здесь проблема?

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

У Redux другой подход, это понятно. Но в статье же речь идет об объектно-ориентированном MVC :)

Так вы же к вот этому комментарию вопрос задавали:
нет смысла его выделять и проецировать использование Redux и React на эту схему
Логика работы редакса про impure. Сам стор работает при помощи грязных функций, с мутабельным изменением стейта

Всё так. Я читал исходные коды. Внутри он impure, разумеется.

Проблема не внутренность, разумеется. Проблема в том, что _интерфейс_ стора (dispatch) — impure, и сам стор — мутабелен для внешнего приложения (с его точки зрения стор — классический синглтон с глобальным, мутабельным состоянием). Если бы импьюрность была эффективно сокрыта за чистым интерфейсом — потроха бы и не были существенны.

Если бы impure были сокрыты за чистым интерфейсом, то это, наверное, какой-нибудь elm получился бы. Впрочем я не уверен (не работал с elm). Что вы пытаетесь доказать? То, что redux-подход не полностью pure? Это так. Кто-нибудь с этим спорит? Или у вас фанатичный подход: "pure everywhere or death"? Мне кажется наш "спор" не имеет никакого смысла.

mapStateToProps, mapDispatchToProps, pureComponent, reducer-s, actionCreator-ы и пр. это что всё про impure

А dispatch уже в редаксе не используется? Диспатч — это про impure. В ооп тоже некоторые методы чистые, это не делает его pure

dispatch реализован не мною. Я его не пишу. Он внутри redux. Мы выше с mayorovp уже все эти косточки перемыли.

Да, я комментировал по ходу, так что не дочитал еще до вашего общения. Тем не менее вы его используете грязно. Именно вы, а не где-то там внутри редакса. Вы вызываете обычную процедуру, чтобы императивно поменять глобальную переменную. `dispatch()` означает «пойди ка поменяй какие-то поля в стейте», обычная императивщина.

Просто я не понимаю, зачем эти хипстерские понты про ФП, если по сути все-равно остаетсь в старой-доброй процедурной парадигме?
Вы вызываете обычную процедуру, чтобы императивно поменять глобальную переменную. `dispatch()` означает «пойди ка поменяй какие-то поля в стейте», обычная императивщина.
А разве dispatch() обрабатывается немедленно прямо при вызове?
А разве делейность как-то отменяет его процедурность? Ну ладно, пусть диспатч будет таким: «запиши ка в один глобальный массив измененения которые ты должен будешь внести потом в другом глобальном массиве».
Смотрите, если диспатч только добавляет в очередь, то
function(dispatch){
  dispatch("aaa");
  dispatch("bbb");
  dispatch("ccc");
}
будет аналогом вызова чистой функции:
dispatch(function(){
  let actions = [];
  actions.push("aaa");
  actions.push("bbb");
  actions.push("ccc");
  return actions;
})
Не, диспатч аналог грязной функции:
global.queue = [];

function(){
  global.queue.push("aaa");
  global.queue.push("bbb");
  global.queue.push("ccc");
}


Вы вообще понимаете отличия pure от impure? У вас ведь нету там return, сами видите, или нет?
Чуть в том, что dispatch «меняет что-то там», вместо «возвращает новые данные». Это называется «side-effect» и он делает функцию impure
Я, вообще-то, так и писал, что «при таких-то условиях эта функция — аналог вызова чистой функции», а не что функция с вызовами dispatch — чистая сама со себе.
Вы меня смущаете. Это вызов грязной функции. Это не аналог вызова чистой функции. Не пишите больше таких глупостей.
Вы приписываете мне то, чего я не писал. Давайте я для вас выделю подлежащее и сказуемое: «Функция — аналог вызова». Не «вызов — аналог вызова», не «функция — аналог функции».
Давайте так: тело этой «грязной» функции можно заменить на вызов «чистой» функции с передачей результата вызова этой чистой функции в dispatch(). Так понятнее, что я имел в виду?
Нельзя, в том то и дело. Если бы можно было, то давно заменили бы. Но редакс не будет работать в функциональном стиле. Серьезно, его не заменили не потому что что-то не додумались, что это аналог вызова функции. Просто вся магия редакса как раз в грязных процедурных сайд-эффектах.

Если не верите, то попробуйте. Напишите работающий код pure диспатча, а не псевдокод. Без сайд-эффектов. И увидите, что ничего не заработает.
Я писал где-то, что dispatch у нас чистый? Откуда вы это находите у меня в сообщениях?
Вы сказали, что диспатч, цитирую: «будет аналогом вызова чистой функции». Как он может быть аналогом вызова чистой функции, если это грязная функция? Вы можете в базовую логику? Здесь нет никакого смысла.

Как могут грязные носки быть аналогом одевания чистых носков?
Вы сказали, что диспатч
Я этого не говорил. Ну прочитайте же вы, наконец, моё сообщение внимательно! Я написал, что функция, вызывающая в теле dispatch, может быть заменена на вызов чистой функции, возвращаемое значение которой потом можно передать в dispatch.
А я говорю, что не может и предлагаю вам лично попробовать заменить, если вы не понимаете, почему не получится.
Да пожалуйста:
Оригинал — грязная функция, вызывающая dispatch:
function dirty(dispatch){
  dispatch("a");
  dispatch("b");
  dispatch("c");
}

Чистая функция:
function pure(){
  let actions = [];
  actions.push("a");
  actions.push("b");
  actions.push("c");
  return actions;
}

Вызов её, функция, взаимозаменяемая с оригинальной грязной функцией:
function dirtyCallsPure(dispatch){
  let myDispatch = actions => actions.forEach(a=>dispatch(a));
  myDispatch(pure());
}

С чем вы теперь будете спорить? С тем, что dirtyCallsPure() сработает аналогично dirty()? Или с тем, что pure() — чистая функция? Или с тем, что в теле dirtyCallsPure() вызывается чистая функция, а результаты её вызова передаются в dispatch()?

dirtyCallsPure правильно назвать dirtyCallsImpure. В нее передается и вызывается грязная процедура dispatch, которая имеет сайд-эффекты. myDispatch, соответственно, тоже грязная. Вообще любое использование forEach уже свидетельствует о грязности, если задумаетесь — поймете почему. pure, конечно, чистая, если не переопределен [].push, но этого мало. Еще одну попытку?

Каким образом сказанное вами противоречит хоть чему-то из сказанного мной? Давайте по пунктам:
1. Функция pure — чистая? Да/нет.
2. Функция pure вызывается в dirtyCallsPure? Да/нет.
3. Если ответы на 1 и 2 — да, то означает ли это, что в dirtyCallsPure была вызвана чистая функция? Да/нет.
4. Если ответ на 3 — да, то разве не это я утверждал? Это/не это…

Функция pure вообще никакого отношения к спору не имеет, она просто пыль в глаза. По сути заменяется просто на массив ['a', 'b', 'c']


function dirtyCallsPure(dispatch){
  let myDispatch = actions => actions.forEach(a=>dispatch(a));
  myDispatch(['a', 'b', 'c']);
}

Теперь убираем myDispatch, который тоже просто пыль в глаза


function dirtyCallsPure(dispatch){
  ['a', 'b', 'c'].forEach(a=>dispatch(a));
}

А теперь убираем forEach, который тоже пыль в глаза, чтобы все прояснить:


function dirtyCallsImpure(dispatch){
   dispatch('a');
   dispatch('b');
   dispatch('c');
}

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


Вы вызываете обычную процедуру, чтобы императивно поменять глобальную переменную. dispatch() означает «пойди ка поменяй какие-то поля в стейте», обычная императивщина.
Функция pure вообще никакого отношения к спору не имеет, она просто пыль в глаза.
Она тут была как самый короткий пример чистой функции. Если вам так хочется докопаться и проводить компайл-тайме оптимизацию ПРИМЕРОВ, то можете поменять на вот такое:
function pure(names){
  return names.map(name=>`Hello, ${name}!`);
}
function dirtyCallsPure(dispatch, names){
  let myDispatch = actions => actions.forEach(a=>dispatch(a));
  myDispatch(pure(names));
}
Повторю и вам, если надо:
Давайте по пунктам:
1. Функция pure — чистая? Да/нет.
2. Функция pure вызывается в dirtyCallsPure? Да/нет.
3. Если ответы на 1 и 2 — да, то означает ли это, что в dirtyCallsPure была вызвана чистая функция? Да/нет.
4. Если ответ на 3 — да, то разве не это я утверждал? Это/не это…
Та насрать на функцию pure, вы ее ввели просто чтобы спрятать свою оплошность с функцией dispatch. В редаксе нету функции pure, там есть функция dispatch и она — грязная.
Та насрать на функцию pure, вы ее ввели просто чтобы спрятать свою оплошность
А, я понял. Вам насрать на то, что я написал, вы спорите с тем, что вы сами придумали.

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


Вы вызываете обычную процедуру, чтобы императивно поменять глобальную переменную. dispatch() означает «пойди ка поменяй какие-то поля в стейте», обычная императивщина

И сейчас хотите свести спор к доказательству того, что вы правы, доказав, что функция foo = () => [ 1, 2, 3 ] чистая. Это грязный прием демагогии, который называется "подмена тезиса".


Еще раз — вы влезли в спор и процитировали, когда я утверждал, что dispatch — императивщина, что это грязная процедура, а не чистая и девственная функция. У вас есть аргументы против этого утверждения? Потому что вас унесло куда-то в фиолетовые дали.

Подмена тезиса — у вас. Я «влез» только спросив про детали вызова dispatch, а потом уже вы мне про моё утверждение стали доказывать, что оно неверное.
тело этой «грязной» функции можно заменить на вызов «чистой» функции с передачей результата вызова этой чистой функции в dispatch()
Нельзя, в том то и дело.
Значит, я вас неправильно понял и вы не пытались отрицать грязную процедурную природу dispatch, а просто задали вопрос по другой теме?
Тогда объясните, пожалуйста, ваш вопрос, потому что я до сих пор не понял его суть. Да, аргументы, которые идут в диспатч можно создать чистыми функциями. Как и любые другие аргументы:
2 + 3
// можно заменить на:

sum = (a, b) => a + b
sum(2, 3)

// можно заменить на:

sum = a => b => a + b
getTwo = () => 2
getThree = () => 3
sum(getTwo())(getThree())

Я ответил на ваш вопрос?
Мой аргумент был про то, что при работе с Redux'ом мой собственный код может быть вполне чистым функциональным, если dispatch будет вызываться инфраструктурой автоматически.
Ну, например, переписать mapDispatchToProps (или написать аналог), который при создание bound action creator'ов будет заворачивать их в аналог моей dirtyCallsPure. Тогда весь бизнес-код с action creator'ами будет чистыми функциями.
Как переписать? Как потом это юзать? Покажите пример, а не гипотетические мечты рассказывайте) В том то и дело, что если бы можно было бы — так и сделали бы.
Ок, вот вам из доков стандартный вид mapDispatchToProps:
const mapDispatchToProps = dispatch => {
  return {
    onTodoClick: id => {
      dispatch(toggleTodo(id))
    }
  }
}
А мы напишем свою обёртку:
const myMapDispatchToProps = functions => dispatch => {
  let result = {};
  for(let i in functions)
    result[i] = (...params) => dispatch(functions[i](...params));
}
Использоваться будет так (как видите, функция onTodoClick теперь чистая, в отличие от оригинальной onTodoClick):
myMapDispatchToProps({
  onTodoClick: id => {
      return[toggleTodo(id)];
    }
})
результат выполнения подсовывается в connect() вместо стандартного mapDispatchToProps.

И что вы будете вызывать?


this.props.onTodoClick()

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

Поменялся мой код — он весь чистый. mayorovp выше приводил пример интерпретатора Хаскеля — рантайм-то нечистый, но язык вполне себе функциональный. Так и тут — весь мой код будет чистым.
Вообще, не понял, чего вы ждали — я это и обещал, что все «нечистые» штуки будут вынесены во фреймворк. Никуда из приложения они, разумеется, не пропадут, просто будут частью инфраструктуры (как рантайм у Хаскеля), а не бизнес-кода.
this.props.onTodoClick() 

Это ваш код, не фреймворковский. И он грязный. он изменяет стейт как обычная процедура. Где ваш код чистый? Вы вообще понимаете, что такое pure?
Мы же рассматриваем вариант, что myMapDispatchToProps() — часть фреймворка, верно? Тгда this.props.onTodoClick — это именно фреймворковский код, а мой — то, что он внутри себя вызывает.

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

Так я с этим и не спорю. this.props.onTodoClick — будет грязная функция. Но она не моя, её фреймворк создал. Я же про это и говорю, что фреймворк сам — не чистый, но писать только грязные функции он не заставляет. Вроде же был же уже консенсус что редьюсеры — чистые функции? Несмотря на то, что во фреймворке они используются чтобы менять глобальное состояние. Я просто предложил способ как такими же чистыми функциями можно писать и код, создающий экшны.

Простите, тогда старый this.props.dispatch() — тоже фреймвоковский код. Как и, впринципе, все, что лично вы пишите не Реакте. Нет, именно this.props.onTodoClick() пишете вы в своем собственном компоненте и именно эта строчка указывает на процедурность всего редакса. Вызов onTodoClick все-еще грязный и вам приходится вызывать его в коде приложения. Без вызова в коде приложения — он не вызовется никогда, т.е. фреймворк сам не знает, когда его вызвать

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

Да, в Реакте setState точно так же делает его обычной императивщиной. И этот же принцип использует Редакс, что так само указывает на его императивный стиль.

А редьюсеры — чистые функции, это не указывает на функциональный стиль? Т.е., Редакс использует смешанный стиль.
А LINQ делает из C# функциональный язык?
Язык С поддерживает чистые функции, но при этом он процедурный язык)

Редакс использует некоторые идеи из FP, да. Как и много чего другого. Черт, та я сейчас игру на Юнити пишу, там столько просто тьма ФП, но я ведь не говорю, что пишу в функциональном стиле. Я пишу в ООП стиле с элементами ФП)
А когда количество ФП превысит 50%, вы продолжите так говорить? Можно тогда такой вопрос: а код на JS, по-вашему, вообще, в принципе — может быть функциональным несмотря на то, что язык поддерживает процедурное и объектно-ориентированное программирование?
Я же писал снизу пример истинно функционального кода на JS. Там 100% ФП. Очевидно, все функциональным быть не может, фреймворк будет процедурным, но клиентский код на таком фреймворке будет чисто-функциональным.

А когда количество ФП превысит 50%, вы продолжите так говорить?

Конечно продолжу, ведь если у меня 60% ФП и 40% ПП, то у меня 2 из 5 функций — грязные. Какое ж это ФП? Как я уже приводил пример — когда в С используешь чистую функцию рассчета корня — ты не используешь ФП, ты просто используешь чистую функцию. ООП и ПП их не отрицают, чистые функции.
Так, знаете, IO, например, в принципе не может быть чистым, значит, и Haskell весь «грязный»? (getLine, вызванная из кода с одними и теми же параметрами вернёт каждый раз разные значения).
> Так, знаете, IO, например, в принципе не может быть чистым, значит, и Haskell весь «грязный»?

Почему же не может? Может.

> Можно тогда такой вопрос: а код на JS, по-вашему, вообще, в принципе — может быть функциональным несмотря на то, что язык поддерживает процедурное и объектно-ориентированное программирование?

redux-saga — чистые (точнее — на них _можно_ писать чисто, можно, конечно, и не).
> Так, знаете, IO, например, в принципе не может быть чистым, значит, и Haskell весь «грязный»?

Почему же не может? Может.
Погодите-погодите, что-то я это пропустил. А как это IO может быть чистым? Что это за IO-функция, которая при одинаковом вызове всегда возвращает одно и то же и не меняет ничего за пределами себя? Та же getLine из Хаскеля — она же возвращает внешний ввод, который не выводится из входных параметров.
Не совсем так.

getLine в Хаскеле возвращает инструкцию для интерпретатора, которая предписывает читать внешний ввод.

Выполняться эта инструкция будет только если ее вернуть из main (возможно, скомбинировав с другими) или если применить к ней unsafePerformIO; до этого момента с ней можно обходиться как с любым чистым значением.
Т.е., с точки зрения самой программы этого IO как бы и нет, есть просто значение, которое подставится при компиляции?
Почему при компиляции-то? При выполнении.

Просто снаружи программы есть примерно такой цикл (ему не обязательно реально существовать на практике — но семантика монады IO именно такова):

1. Вычислить main, получить цепочку из команды и продолжений к ней
2. Исполнить команду
3. Подставить значение которое вернула команда в цепочку продолжений и вычислить ее; получить новую цепочку из команды и продолжений.
4. Перейти к шагу 2.
Получается что-то типа того, как await'ы работают в JS и C# — когда всё, что после await становится новой функцией-коллбэком, которую запустят передав ей результат await'а.
Да, это похоже на них. С тем отличием, что Promise в JS и Task в C# начинают исполняться в момент своего создания. А IO в Хаскеле начинает исполняться только когда до него доходит очередь.
> Что это за IO-функция, которая при одинаковом вызове всегда возвращает одно и то же и не меняет ничего за пределами себя?

Любая ИО-функция в хаскеле.

> Та же getLine из Хаскеля — она же возвращает внешний ввод, который не выводится из входных параметров.

Нет, она не возвращает внешний вывод. getLine возвращает некое _вычисление_, которое (ежели будет запущено) вернет внешний вывод. Вычисление каждый раз одно и то же (можете для простоты представить, что это просто лямбда. каждый раз getLine возвращает одну и ту же лямбду, но не запускает ее). bind (>>=) эти лямбды склеивает в другие лямбды. Потом приложение на хаскеле возвращает большую лямбду main. Все, приложение на хаскеле отработало. Кто там эту лямбду будет запускать (и будет ли) — уже, фактически, не дело хаскеля.

То есть, еще раз — программа на хаскеле заканчивается с возвратом main. Запускает main (и выполняет сайд-эффекты) уже не хаскель (unsafePerformIO невыразимо в семантике хаскеля, такой функции там как бы нет). Мы лишь (в согласии с определенными свойствами семантики хаскеля) можем делать некие утверждения о свойствах процесса выполнения main исходя из свойств хаскель-термов, из которых она составлена.
Я же писал снизу пример истинно функционального кода на JS.
Где именно? Тут?
Код оттуда
initialState = { counter = 1 };

increaseCounter = (state) {
   return { counter: state.counter + 1 };
}

Counter (state) {
   return (
      <div onClick={increaseCounter}>
         {state.counter}
      </div>
   );
}

// Клиент инициирует фреймворк:
FpReact.render(Counter, initialState);

// Код фреймворка:
FpReact.render = (UserMarkup, currentState) { 
   markup = UserMarkup(currentState);
}

// Так фреймворк обрабатывает клики:
var newState = markup.div.onClick(oldState);
Так тут функциональщиной не пахнет — вот это «FpReact.render(…)» функциональный компилятор бы просто выкинул, потому что возвращённое функцией значение не используется.
Мне нравится, как вы ушли в крайность)

Да, вы правы. Это что-то вроде монады IO из Хаскеля. И да, истинно функциональные языки вообще без сайд-эффектов невозможны (точнее возможны, но практического использования не имеют).

Но заметьте что есть разница — процедурно инициировать приложение (а дальше оно работает функционально), или процедурно строить всю архитектуру, где кое-какие места будут работать функционально.

Инициализация — это не логика работы, понимаете?

Ну можно вынести фреймворк на уровень утилиты и запускать из CLI. А в качестве исходной точки ей выдавать json-файл. Тогда все взятки гладки. Комар носа не подточит :)

json файл как инишиалСтейт и функция main, которую автоматически запускает приложение.
Мм, это я ушёл в крайность? Это же вы говорите, что даже часть процедурного программирования делает весь код не-функциональным.
Но заметьте что есть разница — процедурно инициировать приложение (а дальше оно работает функционально), или процедурно строить всю архитектуру, где кое-какие места будут работать функционально.
В моём варианте весь «грязный» код был компоненте и во фреймворке, при этом и редьюсеры, и экшн-креэйторы, и экшн-генераторы были чистыми. У вас редьюсер (increaseCounter) запихнут в компонент, экшнов вообще нет, и всё равно фреймворк и компонент — «грязные».
// Так фреймворк обрабатывает клики:
var newState = markup.div.onClick(oldState);
Под «фреймворк обрабатывает клики» вы предполагаете, что это часть которого фреймворка, Реакта? (а какой ещё фреймворк тут знает всю структуру компонента со всеми хэндлерами?)
Тогда вы просто пошли дальше по моему пути — воспользовались тем, что Реакт всё равно «грязный» и засунули туда («под ковёр») всю «грязную» часть интеграции с Редуксом (вызов экшн-генератора из хэндлера).
Еще раз, при редаксе изменения стейта — грязные. Увы, но это так.
В моем примере (это пример, не готовая либа) изменения стейта, да и весь клиентский код — чистый. И компонент чистый. Посмотрите внимательно — компонент — это чистая функция, которая возвращает объект, он никогда не изменяет стейт.
Увы, но это так. Я вижу, что вы расстроены, мне жаль, но редакс — предоставляет процедурный API с функциональными элементами. Каждый раз, когда необходимо изменить стейт — вы берете и изменяете его вызовом процедуры dispatch, так или иначе. Вон faiwer понимает это и не парится, пользует процедурный редакс дальше.
Абрамов себе понимает, что настоящее ФП его целевой аудитории не зайдет — потому держит баланс — и вот как клево, что большинство выбирает именно редакс при наличии лучшей альтернативы в виде MobX.

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

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

Так вот — может хватить верить в божественную непогрешимость этой библиотеки и, наконец-то, понять его недостатки?
Еще раз, при редаксе изменения стейта — грязные.
Вы имеете в виду вызовы dispatch и вызовы методов из компонента? Да, это так. А с этим кто-то спорил?
И компонент чистый. Посмотрите внимательно — компонент — это чистая функция, которая возвращает объект, он никогда не изменяет стейт.
Ну, раз вы мутацию стейта в обработчик хэндлеров (т.е., в Реакт, или кто теперь у вас хэндлеры к DOM привязывает?) перенесли — то само собой. Реакт(?) стал чуть-чуть «грязнее», потому что он теперь занимается и мутациями глобального стейта, а код стал чистым. Я ведь тоже мог написать у себя в примере, что в реакт-компоненте буду не функции в хэндлерах писать, а объект данных, достаточный для вызова:
return (
  <div onClick={{handler: "onTodoClick", data: {id:1}}}>
    ClickMe!
  </div>
);
И сразу мой компонент стал бы «чистым».
Увы, но это так. Я вижу, что вы расстроены, мне жаль, но редакс — предоставляет процедурный API с функциональными элементами.
Вот опять — а кто это отрицал-то вообще? Ну, разве что я бы скорее сказал, что у него процедурный API для диспатчинга и функциональный — для редьюсеров (раз уж «инициализация не считается»). Но тут большой разницы нет.
Вы имеете в виду вызовы dispatch и вызовы методов из компонента? Да, это так. А с этим кто-то спорил?

То есть мы все это время, все эти десятки сообщений про погоду говорили? Напомню, что вся эта ветка началась с моего заявления:
А dispatch уже в редаксе не используется? Диспатч — это про impure. В ооп тоже некоторые методы чистые, это не делает его pure

И вы только сейчас, наконец-то поняли, что я говорю о Диспатч? Но я ведь с этого начал, смотрите! Так зачем вы со мной спорили, если согласны? Или, просто, вы наконец-то поняли, что таки да — диспатч грязный и стараетесь красивее слиться?

<div onClick={{handler: "onTodoClick", data: {id:1}}}>

И у вас в onTodoClick будет опять вызов процедурного диспатча или его аналога? Что будет в этой функции, которую вы пишете в своем приложении? Вы понимаете, что оттого, что поменяли форму — суть не меняется?
То есть мы все это время, все эти десятки сообщений про погоду говорили? Напомню, что вся эта ветка началась с моего заявления:
А dispatch уже в редаксе не используется? Диспатч — это про impure. В ооп тоже некоторые методы чистые, это не делает его pure
А сможете найти, где я утверждал, что Redux — чистый?
И у вас в onTodoClick будет опять вызов процедурного диспатча или его аналога? Что будет в этой функции, которую вы пишете в своем приложении
У меня в приложении будет вон тот мой чистый onTodoClick:
onTodoClick: id => {
      return[toggleTodo(id)];
    }
А уже фреймворк будет вызывать её по имени и диспатчить возвращённое значение. Так же, как и у вас там, цитирую:
// Так фреймворк обрабатывает клики:
var newState = markup.div.onClick(oldState);
Только у меня он будет делать что-то типа
var newState = dispatch(handlers[onClick.handler](onClick.data), oldState)
Только у меня он будет делать что-то типа

var newState = dispatch(handlers[onClick.handler](onClick.data), oldState)
Ну, или если внутри просто оригинальный Redux, то сделает просто вот так:
function processHandler(handlerDescription){
  var handler = handlers[handlerDescription.handler];
  var data = handlerDescription.data;
  handler(data).forEach(action=>dispatch(action);
}
Как и у вас, весь «грязный» код будет во фреймворке, а весь клиентский — будет «чистым».
Ах, ну да. Но теперь вы уже играете с моей идеей) Зачем вы представляете ее как свою?
Вот только вся соль в том, что редакс так не работает, иначе код на нем был бы чистым. А он работает процедурно и потому код грязный.
Но теперь вы уже играете с моей идеей) Зачем вы представляете ее как свою?
А не вы с моей? Впрочем, неважно. Интересно только, откуда вы взяли, что я вашу как свою преставляю, я же прямым текстом написал:
Я ведь тоже мог
С другой стороны, забавно, что вы собственную же идею не узнали даже после прямой отсылки и набросились с критикой.
Могли, но не сделали же. Я вот мог футболистом стать. Или женщиной.

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

Да, этот onTodoClick — чистый, а вызывали вы другой onTodoClick, который грязный. Вы между ними разницу видите, или нет?

this.props.onTodoClick()


А сможете найти, где я утверждал, что Redux — чистый?

Только сперва в том сообщении, что вы процитировали найдите, где я говорил, что вы говорили, что редакс — чистый. Думаете только вы гаразд глупые задания придумывать?

Переиначу. Пожалуйста, перестаньте придумывать какие-то сообщения и на них отвечать. Если хотите отвечать на мои сообщения — сперва прочитайте их, а потом ответьте на них. На МОИ сообщения, а не на те, которые придумали, пока цитировали мои
Вы между ними разницу видите, или нет?
Вы серьёзно думаете, что я не вижу разницы, но при этом тут же переписал код так, чтобы явного вызова «грязной» функции не было?

Только сперва в том сообщении, что вы процитировали найдите, где я говорил, что вы говорили, что редакс — чистый.
А почему только именно в нём? В нём вы писали, что Redux грязный из-за dispatch, а в другом написали про меня:
Вы сказали, что диспатч, цитирую: «будет аналогом вызова чистой функции»
Итого вы выставили меня защитником чистоты Redux как такового.

Когда на самом деле
Я просто предлагал заменить/разделить целиком грязную функцию А на 2 отдельных части — чистую часть (функцию Б, которая делает всё, кроме вызова dispatch) и грязную часть (вызов dispatch для диспачтинга значения, возвращённого этой чистой функцией Б), чтобы грязную часть можно было вынести с глаз долой во фреймворк


Вот только ваше гипотетическое предложение:
1. Не имело за собой практического примера
2. Не реализуется на практике в редаксе
Не имело за собой практического примера
А насколько «практический» должен был быть пример? И что вы вообще под этим подразумеваете? Вот тут, тут и тут я приводил примеры разной степени «практичности», вплоть до конкретной полной реализации mapDispatchToProps, что с ними не так? Чем оно хуже вашего примера с «вот так оно будет [где-то] в [каком-то] фреймворке» (получалось, прямо в Реакте, который мы, вроде бы, вообще не рассматривали)
Не реализуется на практике в редаксе
Поясните, под «не реализуется» вы подразумеваете «не реализуемо» или «не реализовано»?
что с ними не так?

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

Поясните, под «не реализуется» вы подразумеваете «не реализуемо» или «не реализовано»?

Я подразумеваю, что используя редакс нельзя писать чисто. Потому что изменение стейта через диспатч задумано грязным. И все ваши «вот я дописал еще одну функцию» — все-равно будут оставлять API либы грязным.

Ну нет, все же способ есть, но понадобится вспомогательная функция.


function combineDispatchFactory(dispatch) {
    return {
        combineDispatch(fn) {
            return function(...args) {
                dispatch(fn(...args));
            }
        }
    }
}

Несмотря на то, что этот код — "грязный", функции combineDispatchFactory и combineDispatch являются чистыми.


Использовать их можно вот так:


@connect(..., combineDispatchFactory)
function TodoList({ todos, combineDispatch }) {
  return <ul>
           {todos.map(todo =>
             <Todo
               key={todo.id}
               {...todo}
               onClick={combineDispatch(() => toggleTodo(todo.id))}
             />
           )}
         </ul>
}
Ну довольно близко. Единственно, грязная функция у вас в клиентском коде все-таки проскакивает, пусть и неявно.

@connect(..., combineDispatchFactory)
function TodoList({ todos, combineDispatch }) {
 const dirtyOnClick = combineDispatch(() => toggleTodo(todo.id)); // вот она, в клиентском коде
 return  (
  <ul>
   {todos.map(todo =>
     <Todo key={todo.id} onClick={dirtyOnClick} />
   )}
  </ul>
 )
};


Но попытка хорошая, правда.
Главное — вызова грязной функции нет. А значение, которое просто проскакивает, не отличается от любого другого значения которое просто проскакивает.
Ну с натяжкой. Вот к примеру легендарная отсылка к тому, что ФП легко тестируется. Мой код можно протестировать:
// тут возвращается дельта к аргументу
expect(Counter({ counter: 1 }).div.onClick()) 
  .equals({ counter: 2 })

// оно не имеет сайд-эффектов и зависит только от аргументов
expect(Counter({ counter: 5 }).div.onClick()) 
  .equals({ counter: 6 })

Как тестировать код в вашем стиле? Как процедурный:

var state = { counter: 1 };
// bla-bla, расставляем костыли, подключаем редакс
// тут не возвращается никакого внятного значения
Counter().div.onClick();
// глобальный стейт поменялся процедурно 
expect(state).equals({ counter: 2 }); 
// и вот он снова меняется процедурно 
Counter().div.onClick();
expect(state).equals({ counter: 3 }); 

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

Тут проблема не в отсутствии ФПшности, а в самой идее глобального состояния с которым HOC знает как работать.


Если убрать внешний connect, то все тоже неплохо тестируется, пусть и требует чуть больше слов:


const actions = [];
const combineDispatch = combineDispatchFactory(x => actions.push(x)); // И не надо подключать никакой redux
Counter({ counter: 1, combineDispatch }).div.onClick();
expect(actions).equals([{ type: INCREMENT_COUNTER }]);
Я ведь не говорю, плохо или хорошо тестируется. Я говорю, что тестируется процедурно. Вон посмотрите, у вас в примере тестирование процедурное. Вы тестируете не возвращаемый результат, а поменялась ли переменная.
Намного более простой пример чистой функции — функция `add = (a, b) => a + b`. И она ровно так же не имеет никакого отношения к спору. Повторю вопрос. И прошу, перестаньте уходить от темы и доказывать что-то, о чем мы вообще не общались.

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


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

Видите ваше слово «этой»? Пожалуйста процитируйте, какую именно функцию вы заменили.

Потому что пока вы взяли свою же грязную функцию, заменили строковые аргументы на вызов функции, которая возвращает эти строковые компоненты и начали называть ее чистой
function dirty(dispatch){
  dispatch("a");
  dispatch("b");
  dispatch("c");
}
Я взял функцию, добавил туда вызов чистой функции, потом добавил туда вызов грязной функции. В итоге функция оказалась грязной. Никто, вроде бы, не доказывал, что итоговая функция получилась чистой. Она даже называется dirtyCallsPure — Грязная вызывает чистую.
Если вы думаете, что это нерелевантно — это одно, но вы уже десяток комментариев потратили, утверждая совершенно другое.
Вы ведь хотели заменить тело грязной функции на вызов чистой функции? Цитирую:
тело этой «грязной» функции можно заменить на вызов «чистой» функции

Вы не заменили тело, а добавили просто вызовы чистой функции. Объясните, что вы стараетесь сделать? Вы точно не запутались?
Вы ведь хотели заменить тело грязной функции на вызов чистой функции? Цитирую:
На самом интересном месте цитату оборвали. Знаете — человек состоит из воды, но не только из воды. Если бы я предлагал менять тело функции только на вызов чистой — я бы не стал называть итоговую функцию грязно, как вы думаете?
Ну, и чего минус? Я устал уже — тысячу раз говорю, что предлагал (аналогия) заменить использование грязной посуды на использование чистой, которую потом запачкать, и тысячу раз мне одно и то же в ответ — «как же это вы предлагаете использовать чистую посуду, если вот она у вас пачкается». Как будто я сам не то же самое с самого начала и пишу.
По-моему у вас просто какая-то каша в голове.
Это не аргумент. Вот по-моему — у вас, и что?
Я говорю, что чисто грязную функцию можно разделить на чистую и грязную части, тогда грязную часть (которая будет одинаковая для всех таких функций) можно будет вынести в отдельное место (во фреймворк) и тогда грязный код будет только во фреймворке.
и тогда грязный код будет только во фреймворке

Увы. Такая магия не работает. Грязный код он как инфекция. Всё что к нему прикасается — становится грязным. Единственный способ — это когда сам грязный код вызывает чистый код. Тут всё шито-крыто.

Грязный код он как инфекция. Всё что к нему прикасается — становится грязным.
Но, скажем, интерпретатор Хаскеля — не чистый. Разве это делает весь код на Хаскеле грязным?
это когда сам грязный код вызывает чистый код
Разве это не мой случай? У меня функции будут чистыми, а вызывать их будет фреймворк.
Но, скажем, интерпретатор Хаскеля — не чистый. Разве это делает весь код на Хаскеле грязным?

Если я ничего не путаю, то тут та самая картинка: он будучи грязным дёргает чистый код. Чистый код при этом не пачкается. Декларативщина же.


Разве это не мой случай? У меня функции будут чистыми, а вызывать их будет фреймворк.

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


Сделать часть async-action creator-ов декларативными ― реально. Но не стоит забывать, что на практике они бывают несколько сложнее, чем просто последовательный вызов нескольких более плоских action-ов. И это тоже нужно будет как-то продумывать. Вплоть до DSL :D

Сделать часть async-action creator-ов декларативными ― реально. Но не стоит забывать, что на практике они бывают несколько сложнее, чем просто последовательный вызов нескольких более плоских action-ов. И это тоже нужно будет как-то продумывать. Вплоть до DSL :D
Единственное, что мешает сделать их все чистыми — это IO. Но тут даже Хаскель грешен.

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

А как такие вещи делаются в расово-верных функциональных языках? Скажем есть у меня некий UI, и есть там 1 кнопка. И мне нужно как-нибудь навязать на эту кнопку смену глобального стейта. Там тоже будет impure или умные дяди придумали как всё сделать по феншую?

Там тоже будет impure или умные дяди придумали как всё сделать по феншую?

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

initialState = { counter = 1 };

increaseCounter = (state) {
   return { counter: state.counter + 1 };
}

Counter (state) {
   return (
      <div onClick={increaseCounter}>
         {state.counter}
      </div>
   );
}

// Клиент инициирует фреймворк:
FpReact.render(Counter, initialState);

// Код фреймворка:
FpReact.render = (UserMarkup, currentState) { 
   markup = UserMarkup(currentState);
}

// Так фреймворк обрабатывает клики:
var newState = markup.div.onClick(oldState);


Конечно, стейт будет сохраняться в переменную, то есть чистота на уровне фреймворка не получится, но тут будет именно та ситуация, которую приводит Chamie с Хаскелем — клиентский код будет 100% чист.

С другой стороны, такой фреймворк накладывает свои ограничения. Например, onClick будет работать исключительно со стейтом.

В принципе можно заморочиться и развить эту идею дальше. Скажем в onClick передавать более сложную структуру, в которой будут и другие методы. Например один будет формировать параметры для actionCreator-а, другой будет делать какую-нибудь проверку и на основе неё решать нужно ли формировать новый state, ну и т.д.


Но чем дальше в погреб, тем больше наш handler будет напоминать реальные JS-хандлеры )

Это да, не факт, что это будет удобно. Но Редакс же юзают. И большинство его юзает просто потому что модно и функционально. До удобства там далеко. Серьезно, копипастят тысячи одинаковых екшенов, называют абстракции злом и считают эти сотни тысяч строк кода удобным, хоть и сложным приложением.
если вы вызываете грязную функцию фреймворка, то это ВЫ вызываете грязную функцию фрейворка, используете сайд-эффекты и все такое.
Я и использую. Но грязным в итоге получается только Реакт-компонент — больше у меня никто не вызывает грязный код.
Эдак можно любую грязную ф-ю f помощи грязной ф-и g заменить чистой ф-ей id: g(f) = id(f)();
Расскажете, как? А то у меня в итоге функция грязная выходит, я её даже так и назвал dirtyCallsPure.
Нет, не будет. Потому что надо чтобы кто-то потом что-то делал с данными из очереди и этому кому-то нужна будет новая очередь. Придется мутабельно заменить очередь, и сделать это особо негде, кроме как при вызове dispatch. Если, конечно, обрабатывать очередь предполагается клиенту — то да, будет чисто.
Не понял, распишите подробнее, пожалуйста. В общем случае очередь не обязана быть общей, это же просто массив экшнов (объектов с данными), по результату выполнения action creator'а Redux получает просто массив с очередной пачкой экшнов, с которыми уже поступает на своё усмотрение — может добавить куда-то (в какую-нибудь другую очередь, например, возможно и глобальную), может разбить.
Ну как вы из нескольких разных точек получите один массив, который надо выполнить внутри редакса? Вы имеете ввиду потом руками как-то объединить? А потом что? Надо же дернуть «посчитайка новый стейт на данном массиве» и потмо обновить стейт.
Надо же дернуть «посчитайка новый стейт на данном массиве» и потмо обновить стейт.
Да, и замена ссылки на новый стейт будет единственной мутацией на всю обработку очереди.

Да, именно так. Если, конечно же, нет мидлвари которая отложит его вызов.

TheShock да причём тут понты. Это скорее страдания :) Но не без своих преимуществ. Вы же не будете говорить, что подход к реализации одного и того же проекта во Vue и в Redux+React будут одинаковыми? 95% кода redux-react разработчик пишет как раз pure.


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

Мне кажется, что не стоит смешивать Редакс и функциональный подход каждый раз. Ну вот серьезно, Редакс больше про процедурный подход. Чистые функции всегда везде использовались. Потому что подобные заявления похожи на «У нее было всего 10 мужчин до меня, потому с ней я узнал, каково это быть с девственницей».
Просто скажите — сколько у вас тысяч экшенов?

<1k. Я не разработчик MS Office. Мне кажется если речь идёт о тысячах action-ов, то речь идёт о просто громадных приложениях. Полагаю, что там одного только JS будет на несколько 10-ов MiB в пожатом виде. Либо какой-то другой подход к action-ам.


Вот скажем взять gmail. Много ли там тысяч action-ов (если бы он был на redux)? Я думаю <1k. Откуда столько. А вот какой-нибудь Office 365 запросто. Однако в случае модульной архитектуры (а какая там ещё может быть архитектура) я не думаю, что это сильно скажется на сложности отладки. Возможно я ошибаюсь. В любом случае это какое-то около-космическое число для SPA.


У вас есть просто метод, который вызывает другие методы, последовательно и ясно.

Мой опыт говорит строго об обратном. У вас есть метод, который чёрный ящик. Вы открываете его и пристально его изучаете. Он вызывает методы 1, 2 и 3. Какие-то асинхронно, какие-то нет. Каждый из них тоже чёрный ящик. Ну и т.д. Всё мутабельно, часть асинхронна (причём непредсказуемо), и сильно вложенно.


у вас спавнится экшн, который спавнит другие экшны, которые вызывают изменения в сторе

Чаще всего у нас спавнится action, который просто объект. И он ничего не спавнит более. Либо это async-онный action, в который опять же последовательно спавнит другие action-ы, и там обычно даже по их названиям всё понятно. Таких очень мало. В некоторых проектах пренебрежимо мало. Держать замороченную логику в async-action мягко говоря моветон.


Это, с-но, и есть mvc. Что flux, что редакс — это варианты реализации mvc.

В статье всё не так. Совсем не так. Данные лежат в модели. Прямо в ней. Среди методов. Которые работают не с абстрактными данными. Которые работают со своими данными. Каждая модель это отдельный instance класса Модели.


Так кто говорит про то, что каждая модель — своя крепость со своим уставом?

Мне других вариантов не попадалось пока. Проект растёт. Подходы меняются. Люди меняются. Вот скажем взять этот TODO из поста. Я не могу даже бегло оценить над чем оперирует каждая модель пока пристально её не изучу. Чем не чёрный ящик? Сегодня автор её написал в одном стиле. Завтра что-нибудь заоптимизировал — стало всё по-другому. Получаются такие слои проекта. И если проект достаточно долгий то за счёт этакой анархии мы приходим к тому, что у нас много проектов внутри одного и мы боимся хоть к чему-нибудь прикоснуться.

В любом случае это какое-то около-космическое число для SPA.

Пример из личной жизни. Админка для онлайн СМИ.
Которая состоит из:
1) Подсистемы Управления Контентом
2) Подсистемы Планирования Выпуска
3) Подсистемы Статистики и Аналитики
4) Подсистемы Управления Пользователями
5) Подсистемы Доставки Контента
Каждая из этих 5 подсистем состоит в админке из нескольких экранов по функционалу не проще чем экран Gmail а иногда и сложнее.
При этом данные между подсистемами естественно имеют связи. Вот и представьте.
И это еще далеко не Enterprise.
Вот и представьте

Если честно не получается. Многим из вышеперечисленного я занимался. Не пойму где вы там тысячи action-ов нашли. Наверное у вас другой подход к их формированию.


Разве что раздел статистики. Скажем вы сталкивались с Google Analytics? Необъятно большое приложение. Охотно верю, что там речь идёт как раз на тысячи. Но, согласитесь, такие большие приложения это большая редкость.

Скажем вы сталкивались с Google Analytics? Необъятно большое приложение.

С точки зрения интерфейса? Я бы сказал «среднего размера» приложение.
> <1k. Я не разработчик MS Office. Мне кажется если речь идёт о тысячах action-ов, то речь идёт о просто громадных приложениях.

Одна единственная сложная форма уже требует нескольких сотен экшонов.

> Мой опыт говорит строго об обратном. У вас есть метод, который чёрный ящик. Вы открываете его и пристально его изучаете. Он вызывает методы 1, 2 и 3. Какие-то асинхронно, какие-то нет. Каждый из них тоже чёрный ящик. Ну и т.д. Всё мутабельно, часть асинхронна (причём непредсказуемо), и сильно вложенно.

В случае с редаксом все то же самое, только логика обработки у вас размазана между редьюсерами, middleware, асинхронным экшеном и action creator и вы _не можете_ автоматически, по клику в иде перейти между этими кусками.

> Чаще всего у нас спавнится action, который просто объект.

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

> Держать замороченную логику в async-action мягко говоря моветон.

А где ее, извините, делать? Других мест редаксом не предусмотрено. И если делать в другом месте — зачем вообще нужен редакс, если он никак не помогает снизить сложность?

> В статье всё не так. Совсем не так. Данные лежат в модели. Прямо в ней.

Все верно. стор+редьюсеры — это модель.

> Вот скажем взять этот TODO из поста. Я не могу даже бегло оценить над чем оперирует каждая модель пока пристально её не изучу.

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

Кажется я понял вас. Просто если скажем на форме расположено 25 <input/>-ов, то у меня это будет 1 action. Мне лень писать export const setFirstName = ({ firstName }) => ({ type: SET_FIRST_NAME, firstName }). У меня это будет setFormField. Возможно там, где у вас 1000 экшнов, у меня будет 15. К тому же даже очень сложные формы обычно очень однообразны. Самая скучная часть frontend работы. Мне повезло больше, я таким редко занимаюсь.


только логика обработки у вас размазана между редьюсерами, middleware, асинхронным экшеном и action creator

Прошу обратить внимание на то, что она хоть и вымазана (это правда сильно бесит), но вымазана она строго определённым образом. Вариативности очень мало, либо нет вовсе.


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

А это вопрос лишь развития IDE. Ничего не мешает IDE делать это успешно в 99% случаев. Логика для этого достаточно проста. Все пути к actionCreator-ам прописаны прямо в export-import. Все reducer-ы также на них ссылаются. Т.е. всё более чем реально. Может даже есть готовые плагины на этот счёт. А вот для произвольной модели такого сделать невозможно в принципе.


Все верно. стор+редьюсеры — это модель.

Я говорю про решение автора. А не про redux. Там данные лежат прямо в классе.


Потому что модель ни над чем не оперирует. Вы ищите черную кошку в черной комнате, когда ее там нет.

switchStatus() { 
        this.completed = !this.completed
        this.todoList ? this.todoList.todoUpdated() : this.updateViews();
    }

А что она делает?

> Кажется я понял вас. Просто если скажем на форме расположено 25 <input/>-ов, то у меня это будет 1 action. Мне лень писать export const setFirstName = ({ firstName }) => ({ type: SET_FIRST_NAME, firstName }).

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

> Самая скучная часть frontend работы. Мне повезло больше, я таким редко занимаюсь.

Это, конечно, очень удобно, объявить сложные задачи — скучным и невезением. Только результат-то в сухом остатке один — когда нужен сложный интерфейс (как по наполнению так и по логике работы), то редакс = беда. А когда вам просто надо нарисовать 25 сраных инпутов и отослать на сервер — здесь конечно проблем нет. Потому что это само по себе просто, хоть с редаксом, хоть без него.

> А что она делает?

Модель работает сама на себе. «За моделью» или «под моделью» ничего нет. Она и есть самый задний и нижний уровень (по крайней мере на данном уровне абстракции).

> А это вопрос лишь развития IDE. Ничего не мешает IDE делать это успешно в 99% случаев.

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

Почему каждый? Просто 3 action-а. На всё приложение. Зачем вам столько копипасты?


практически на каждое поле и тоже иногда асинхронная

И вы снова будете на каждое поле писать отдельный action? Даже несмотря на то, что они идентичны практически символ-в-символ?


по реакции на всякие чекеры и т.п. меняется flow формы

Если имеется ввиду показывать-не-показывать часть формы при нажатой-отжатой галочке, то для этого нужно ровно 0 action-ов. Достаточно того самого setFormField. Для более сложных случаев надо знать конкретную ситуацию.


Модель работает сама на себе. «За моделью» или «под моделью» ничего нет. Она и есть самый задний и нижний уровень (по крайней мере на данном уровне абстракции).

Ну о том и речь. В статье нет разделения на данные и на код, который их обрабатывает. Это одна сущность. Вы выше пытались это оспорить. В redux (и наверное MobX) данные отдельно, код отдельно. Модели можно даже менять, если они работают с одним и тем же форматом данных store. Своего у них ничего нет. Но это в redux. Там есть такое разграничение.


т.к. никаких жесткий правил по реализации того, как вы пишите редьюсеры/экшены/экшен-криэйторы нет.

Это в целом в JS так. Чем больше динамики (всякие геттеры, сеттеры, фабрики и пр.) тем меньше будет помогать IDE. Хотите чтобы было лучше? Используете меньше магии, либо пишете свои плагины.

> Почему каждый? Просто 3 action-а. На всё приложение.

Потому что это разные запросы (зачастую — именно разные типы, с совершенно разным характером возвращаемых данных) и реагировать на них надо по-разному. Или, погодите, вы предлагаете вместо большого количество экшенов (каждый из который соответствует семантически некоторому осмысленному действию) завести экшен вида FormState (который будет содержать в качестве пейлоада весь новый стейт формы) и для него один редьюсер, который будет делать (x => return x.payload)?

> Это в целом в JS так. Чем больше динамики (всякие геттеры, сеттеры, фабрики и пр.) тем меньше будет помогать IDE.

С просто методами — достаточно неплохо помогает, особенно если это ts.

> И вы снова будете на каждое поле писать отдельный action? Даже несмотря на то, что они идентичны практически символ-в-символ?

Почему это они идентичны? Разные валидаторы, которые применяются по-разному и результат которых обрабатывается по-разному.

> Если имеется ввиду показывать-не-показывать часть формы при нажатой-отжатой галочке, то для этого нужно ровно 0 action-ов.

То есть, вы предлагаете вынести эту часть состояния из редакса, так?

> В статье нет разделения на данные и на код, который их обрабатывает. Это одна сущность.

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

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


пейлоада весь новый стейт формы

Нет, скорее так: export const setMyUglyFormField = ({ field, val }) => ({ type: '...', field, val });.


Почему это они идентичны? Разные валидаторы, которые применяются по-разному

Это нюансы реализации уже редьюсера, а не action-а. В reducer-е есть доступ ко всем полям формы. Валидируй-не-перевалидируй. Если для валидации может потребоваться сделать запрос — его сделает action. Но городить для этого кучу action-ов нет никакого резона. У action-ов есть параметры.


и результат которых обрабатывается по-разному

У вас там форма или цирк? Что значит по-разному? Даже если там есть 1-2 вида это тоже формализуемо.


То есть, вы предлагаете вынести эту часть состояние из редакса, так?

Разумеется. Зачем вы её туда затолкали? Это вообще не redux-way. Всё что может быть вычислено/определено по данным из store, не должно лежать ни в reducer-ах, ни в action-ах. Не должно быть в store никаких boolean полей типа: hideOrderDetailsForm, если они вычисляемы из данных store-а.


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

Кажется вы не понимаете redux. Разница огромная. В redux вы даже можете применить IoC (просто потому всё, что работает с данными это pure и расположено отдельно, не прибито гвоздями).

> Нет, скорее так: export const setMyUglyFormField = ({ field, val }) => ({ type: '...', field, val });.

Ну и у вас по куче экшенов на форму. Не понял, что вы тут выиграли?

> В чём та разность возвращаемых данных?

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

> Но это никак не сказывается на кол-ве action-ов.

Ну как же не сказывается? На любой асинхронный запрос вам надо по три (минимум) экшона — на старт, на success и на error. Каждый атвокомплит — это (условно) свой асинхронный запрос (всякое кеширование и т.п. оставим за кадром)

> Если для валидации может потребоваться сделать запрос — его сделает action. Но городить для этого кучу action-ов нет никакого резона. У action-ов есть параметры.

Какая разница, различаете вы тип экшона по полю type или по характеру значений в пейлоаде? В обоих случаях это семантически разные экшоны с разной обработкой. Нет никакой разницы между if (action.type == ...) {} и if (action.payload.field ....) {}.

> то нюансы реализации уже редьюсера, а не action-а. В reducer-е есть доступ ко всем полям формы. Валидируй-не-перевалидируй.

Чтобы завалидировать поле в редьюсере, надо отправить экшон в этот редьюсер.

> У вас там форма или цирк? Что значит по-разному? Даже если там есть 1-2 вида это тоже формализуемо.

Ну то и значит. В зависимости от того, что там за поле валидировалось, могут быть активированы/деактивированы те или иные контролы скрыты/показаны те или иные куски форм, выведена/сокрыта та или иная дополнительная информация, сделаны те или иные дополнительные запросы к серверу. То есть вам нужна на каждое валидируемое поле, как минимум, ветка в редьюсере — то есть экшон (как и выше не различаем if (action.type == ...) {} и if (action.payload.field ....) {})

> Всё что может быть вычислено/определено по данным из store, не должно лежать ни в reducer-ах, ни в action-ах. Не должно быть в store никаких boolean полей типа: hideOrderDetailsForm, если они вычисляемы из данных store-а.

Так и нельзя вычислить, это ввод пользователя. Поставил он галку или нет. В зависимости от этого что-то происходит. hideOrderDetailsForm в сторе, конечно, не лежит, лежит состояние чекера.

> Кажется вы не понимаете redux. Разница огромная.

Разницы нет. Все, что можно в первом случае — точно так же делается во втором. И наоборот. Это абсолютно эквивалентные подходы, которые выражаются друг через друга.
Ну и у вас по куче экшенов на форму. Не понял, что вы тут выиграли?

Один action. Я там указал, что он принимает на входе имя поля (точнее там должен быть path)


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

Какое это имеет отношение к action-ам? Зачем это знать action-у? Причём тут кеширование? Причём тут внутренности работы запроса? Давайте ещё на backend перейдём и начнём нюансы redis обсуждать.


Какая разница, различаете вы тип экшона по полю type или по характеру значений в пейлоаде? В обоих случаях это семантически разные экшоны с разной обработкой.

Эта разница лежит не в action-е. Эта разница лежит в редьюсере. Action как раз универсален. Вы ещё начните писать setAge12, setAge13, setAge14. Не надо экшнами код писать, это дичь. Экшны это высказывание намерений о том, какого рода событие произошло, чтобы reducer на это отреагировал.


В зависимости от того, что там за поле валидировалось, могут быть активированы/деактивированы те или иные контролы скрыты/показаны те или иные куски форм, выведена/сокрыта та или иная дополнительная информация

Action-ы к этому НЕ имеют НИКАКОГО отношения. Вы ооочень странно готовите redux.


лежит состояние чекера.

И этот чекер управляется всё тем же самым единственным setField('myCheckerName', true).


Можно вопрос? Вы по роду деятельности пишете на Java 6? :)

> Эта разница лежит не в action-е. Эта разница лежит в редьюсере. Action как раз универсален.

Обычно редьюсер определяет тип экшона (и логику совю) по полю type. Вы предлагаете просто вместо поля type использовать другое поле. У вас не стал тут «один экшон», вы просто переместили квалификатор типа из одного поля в другое. Логика для каждого типа все равно в редьюсере будет своя. И все равно ее трудно будет отслеживать.

> Какое это имеет отношение к action-ам? Зачем это знать action-у? Причём тут кеширование? Причём тут внутренности работы запроса?

Потому что эта логика в асинхронном экшене и по результатам ее работы спавнятся экшоны.

> Action-ы к этому НЕ имеют НИКАКОГО отношения. Вы ооочень странно готовите redux.

Экшоны имеют отношение к действиям пользователя и реакции внешней среды (ответ от сервера, например). Ввод данных в поле Х и поле Y — два действия пользователя. Разных. Если на два разных действия пользователя надо реагировать (в редьюсере) существенно по-разному, то эти два действия семантически соответствуют разным экшонам (вы где угодно и как угодно можете кодировать тип экшона, главное, что в редьюсере будет полноценная ветка обработки этого типа). Если результат валидации порождает разные существенные последствия (а не только те, что можно обобщить в виде «вывели под элементом Х сообщение об ошибке Y», например), то вам нужна эта ветка в редьюсере, а значит у вас _по факту_ есть +1 тип экшонов. Еще раз — вы можете как угодно и где угодно кодировать этот тип, но это экшон нового типа де-факто, т.к. на него есть уникальная реакция редьюсера.
У вас не стал тут «один экшон», вы просто переместили квалификатор типа из одного поля в другое

Незачем эти вещи делать в action-ах. И незачем городить для этого такой огромный зоопарк.


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

Мне не нравится такая формулировка. Дело в том, что мозг приложения это как раз редсьюсеры. И ветвления кода там это более чем-нормально. Особенно если речь идёт о примерно одинаковых вещах, с небольшими различиями. Более того, никто не заставляет вас в reducer-ах писать switch-ы. Вы можете обсустраивать там логику как вам душе будет угодно. И очевидно, что группировать однотипные, но всё же отличные в мелочах вещи, там будет проще. И писать такое проще. И читать такое проще. И копипасты примерно 0. Откуда вам вообще пришла в голову мысль для каждого setter-а городить отдельный action? Из java? :)


Логика для каждого типа все равно в редьюсере будет своя.

Только кастомная логика будет своя. Да и то, скорее всего расгруппирована по смыслу.


Потому что эта логика в асинхронном экшене и по результатам ее работы спавнятся экшоны.

Если архитектуру писал не орангутанг, то это более чем формализуемо. К примеру валидацию можно дёргать по тому же имени поля, передавая туда все нужные параметры. Это будет краткий и универсальный код. Это более чем просто. Сами нюансы запросов и их реализации вообще к этому отношения не имеют.


Ввод данных в поле Х и поле Y — два действия пользователя. Разных. … и многое другое

Нет, это не так. Вы сами придумали эту проблему и теперь героически с ней сражаетесь. Следующий шаг ― setAge13.

> Незачем эти вещи делать в action-ах.

А где, прошу прощения? Единственный способ обновить данные в сторе — дернуть редьюсер через диспатч экшона. Единственный способ избавиться от экшенов — убрать данные из стора.

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

В редьюсерах только самая тривиальная логика. Мозг — асинхронные экшоны.

> Более того, никто не заставляет вас в reducer-ах писать switch-ы.

Да какая разница свитчи, ифы или еще как? Смысл в том что в редьюсере должен быть кейз с обработкой.

> И очевидно, что группировать однотипные, но всё же отличные в мелочах вещи, там будет проще. И писать такое проще. И читать такое проще.

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

> Откуда вам вообще пришла в голову мысль для каждого setter-а городить отдельный action?

Оттуда, что никакого другого способа обновить стор — в редаксе нет. Только через отдельный экшн.

> Только кастомная логика будет своя.

Ну она и есть обычно кастомная.

> Если архитектуру писал не орангутанг, то это более чем формализуемо.

Формализуемо что?

> К примеру валидацию можно дёргать по тому же имени поля, передавая туда все нужные параметры.

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

> Это будет краткий и универсальный код.

Он будет универсальный, только если вам нужно делать _одно и то же_.

> Нет, это не так.

Это так. Как минимум, с точки зрения самого пользователя. И, конечно же, пользователь ожидает, что на разные (по его мнению) действия приложение будет реагировать по-разному.
код в … асинхронных экшенах
Как вы код в пакеты с данными запихиваете? Экшн — это же просто данные.
Асинхронный экшн — не только данные, конечно же. На то он и асинхронный.
Так он и не экшн тогда.
Как не экшн? Все, что можно засунуть в dispatch (и с чем он корректно отработает, конечно) — экшн.

Druu, относитесь к экшнам как plain-object, даже если они вынужденно-асинхронные. Избегайте туда писать любую логику. Прямо по пальцам линейкой… Чем тупее async-action, тем лучше. Вплоть до замены его на какой-нибудь декларативный middleware.


Задача action-а в экомистеме redux-а только одна: "выразить намерение". Сказать reducer-у, что неплохо было бы сделать что-то. Чем декларативнее, тем лучше. Поэтому почти всегда (в некоторых приложениях 100%) они представляют из себя plain object.


Это не место для логики. И уж тем более для бизнес-логики. Мы вынуждено пишем там наши await api.someApi(). Это такой грязный костыль. И он там только по той причине, что никто (вроде) ещё не придумал как сделать лучше. В reducer-е этого нет, т.к. это святая святых. Он просто обязан быть pure (reducer), поэтому туда никак. А больше, по сути и некуда.


Файлы с action-ми должны быть предельно худыми. Вся логика в редьюсерах (и вспомогательных к ним файлах). Это redux way.

А что не так с асинхронными экшн-креэйторами?

А что с ними так? :) Их даже нет в стандартной поставке redux-а. Имеют массу реализаций. Я использовал только thunk. Он по сути пробрасывает туда dispatch и getState, даёт полный карт-бланш. Практически абсолютная свобода. Вместо простого флажка "я хочу" мы получаем некий god-mode. И всё это ещё и асинхронно. Прервав его выполнение посередине (например какой-нибудь reload таба) мы получаем ерунду в store.


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

А что с ними так? :) Их даже нет в стандартной поставке redux-а
А что там должно быть «в поставке»? Написать async в сигнатуре функции — это нужно что-то поставлять?
Если просто написать асинк в сигнатуре ф-и, то стор работать не будет.
В сигнатуре функции, создающей bound action creator.

Судя по всему, Chamie имел ввиду использовать следующий трюк:


  • используем react-redux
  • используем mapDispatchToProps в виде нотации dispatch => object
  • в object в качестве actionCreator-в используем async actionCreator-ы которые за-bind-ны на dispatch

Вся эта наркомания работает. Только что проверил. Никогда бы не подумал, что кто-нибудь будет использовать "такое" в деле.

Вы так пишете, будто это что-то сложное и нестандартное, а не просто добавить 1 слово (async) в стандартное приложение на react-redux.

Ну потому что это не добавить 1 слово async в стандартное приложение. Вот почему. Это организовать все action-ы строго через mapDispatchToStore, да ещё и пере-bind-ив их на конкретный экземпляр dispatch. Ну либо прямо написав их внутри этой mapDispatchToStore фабрики и таким образом, воспользовавшись замыканием. А то, что при этом это работает в обход redux-а, не принципиальный момент? То что вызвав store.dispatch(asyncAction()) у вас приложение упадёт с ошибкой (т.к. это вообще не action) — это разве мелочь?


Вы можете вообще отдельно написать какой-нибудь метод, которому какими-нибудь правдами и неправдами будет доступен метод dispatch(например через глобальную переменную). И тогда у вас снова будет "асинхронное поведение". Но асинхронным action-ом это, конечно, не станет.

А то, что при этом это работает в обход redux-а, не принципиальный момент? То что вызвав store.dispatch(asyncAction()) у вас приложение упадёт с ошибкой (т.к. это вообще не action) — это разве мелочь?
Вы путаете. Эта функция — не action creator, поэтому её возвращаемое значение и нельзя задиспатчить — она не возвращает action.
Но асинхронным action-ом это, конечно, не станет.
Я, вообще-то, именно про это и писал — мол, зачем создавать какие-то асинхронные action'ы, если можно просто вызывать dispatch обычных action'ов в асинхронном коде?
Эта функция — не action creator, поэтому её возвращаемое значение и нельзя задиспатчить — она не возвращает action

Я ничего не путаю. Всё верно. Эта функция не action-creator, как можно подумать изначально. Мы ведь типа в mapDispatchToStore должны список actionCreator-ов передать. Но… нет. Можно и всякую хрень туда слать. Оказывается оно пережуёт.


если можно просто вызывать dispatch в асинхронном коде?

Грубо очень. Вообще не по-феншую :) Это же по сути кусочки бизнес-логики… Которые по идее должны лежать 2 стадиями дальше. Но их туда не положить. Получается чепуха какая-то. И в итоге мы явным образом во View вызываем руками самостоятельно методы с бизнес-логикой, вообще в обход framework-а.


У этого подхода я вижу только 1 плюс: ну оно и правда работает.

Мы ведь типа в mapDispatchToStore должны список actionCreator-ов передать.
Имеете в виду mapDispatchToProps? Нет, не так. В доках пишут, что мы должны передать туда не список actionCreator'ов, а функцию, которая принимает dispatch и возвращает список функций, которые будут вызывать actionCreator'ы и этот переданный ей dispatch.
Скриншот из доков Redux

Лезу в репу и читаю:


If an object is passed, each function inside it is assumed to be a Redux action creator

А ну всё ок. Он кормится всё таки actionCreator-ми. ЧТД. Но погодите...


If a function is passed, it will be given dispatch as the first parameter. It’s up to you to return an object that somehow uses dispatch to bind action creators in your own way

Oh shit… Не кормится. Тут у нас лютый треш с somehow uses dispatch. "магия.jpg".


О чём и речь. У меня цензурных слов на это слов нет :)

Да нет, тут все понятно. Если параметр mapDispatchToProps — объект (не функция которая возвращает объект, а просто объект!) — то он должен состоять из голых actionCreator-ов. А если он функция — то она должна вернуть объект, состоящий из частичных применений композиций dispatch и actionCreator-ов.
состоящий из частичных применений композиций dispatch и actionCreator-ов

Не согласно документации всё так и есть. Я и не говорил, что это "непонятно". Я скорее выразил, что я думаю об этих композициях над dispatch в рамках этой экосистемы.

Ну, т.е., это документированное поведение, позволяющее подключать туда произвольный код, в т.ч. асинхронный бизнес-код, без привлечения доп. фреймворков ;)
It’s up to you to return an object that somehow uses dispatch to bind action creators in your own way
Т.е., «это уже ваше дело будет вернуть объект, который сможет как угодно использовать dispatch, и прибайндить туда action creator'ы так, как вам удобнее».

На самом деле я могу и так сделать:


setTimeout(() => window.store.dispatch({ type: 'KILL_ALL_HUMANS' }), random());

Это примерно в той же степени моё дело ;)

> Druu, относитесь к экшнам как plain-object, даже если они вынужденно-асинхронные. Избегайте туда писать любую логику. Прямо по пальцам линейкой… Чем тупее async-action

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

> Вся логика в редьюсерах (и вспомогательных к ним файлах).

Это ведет исключительно к дополнительному распуханию количества экшонов и усложнению логики. Лучше просто написать по порядку в экшене три асихнронных вызова (три строчки кода, просто и понятно, ну или вынести их в отдельную ф-ю, несущественно), чем городить несколько экшенов для этого, и пробрасывать их через редьюсер для апдейта стейта, в результате чего три строки превратятся в 30 с размазанной кусками логикой.

> Мы вынуждено пишем там наши await api.someApi()

Так об том и речь. Когда у вас много логики на асинхронщине — редакс превращается в говно, а на практике в СПА _много_ асинхронной работы со стейтом (ну вот вам везет, у вас мало).
редакс превращается в говно

Всё что угодно превратится в оное, если его так причудливо выворачивать ;) Ваш подход работы с redux-ом напоминает мне питание через клизму.

> Всё что угодно превратится в оное, если его так причудливо выворачивать

А есть способ делать асинхронные вызовы при использовании редакса, ничего не выворачивания (саги не в счет, потому что они — не редакс де-факто)?

> Ваш подход работы с redux-ом напоминает мне питание через клизму.

Ну покажите как работать в редаксе с асинхронщиной не через клизму.

Есть. Писать худые action-ы, оставляя в них минимально необходимое (вызовы async-api). Полученные данные в plain-object action-а сплавлять в reducer.


validateField = async ({ field, val, extra, isAsync }) => dispatch =>
{
  const validationInfo = isAsync && api.getValidationInfo(field, val, extra);
  dispatch({ type: 'validate', field, validationInfo, val, extra });
}

Что мы имеем? 1 экшн в 2 строки. Конфигурируется 2 полями: extra — все гипотетические поля, которые могут потребоваться для валидации поля (явно указываются к примеру при декларации). Если не нужны — не декларируются. Какой именно сделать запрос к бекенду (если надо) решит api (а не action, боже мой), отдельная сущность для запросов. Скорее всего там будет универсальный примитив. Ну типа await fetch.post('/api/validate/${formKind}/${formField}', { info }).


В редьюсере тоже организовать всё просто. К примеру можно указать перечень полей, которые будут проверяться на ^\d+$/. Или явным образом список ограничений в опрятном виде передавать прямо в <Control/>. Способов в общем много. Суть одна. Не писать по 300 раз бойлерплейт а зайти через конфигурацию. Вопрос будет лишь в том, где будут лежать конфиги на поля. Это уже спорно.


И не будет 1000 if-ов в reducer-е. И не будет 1000 action-ов. Всё будет относительно компактно и опрятно. Правда потом сложно будет сказать заказчику, что у нас огромный проект из копипасты на 20000 экшнов :) Он будет маленький и без копипасты.

> Есть. Писать худые action-ы, оставляя в них минимально необходимое (вызовы async-api).

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

> И не будет 1000 if-ов в reducer-е. И не будет 1000 action-ов.

Будет и 1000 ифов и 1000 экшонов. Еще раз — если у вас есть 1000 разных кусков кода, каждый из которых должен выполниться при определенных обстоятельствах, то у вас будет ветвление на 1000 кейзов в том или ином виде и 1000 экшонов на запуск каждого куска. Это просто математический факт, ну о чем тут можно спорить, ей-богу?
Это просто математический факт, ну о чем тут можно спорить, ей-богу?

Вот я и думаю, о чём? Если вы привыкли писать вот так, или вот так, ну или вот так… То это может быть не очевидным, что обычно люди группируют однообразную функциональность в одном месте. И тогда, внезапно, получается, что даже при некотором различии в обработке конкретных полей данных, их обрабатывает 1 участок весьма компактного универсального кода, а не 1000. А ввиду того, что JS это язык со слабой динамической типизацией, это всё реализуется за 1 присест.

> То это может быть не очевидным, что обычно люди группируют однообразную функциональность в одном месте.

Да при чем тут однообразная функциональность? В каждом случае логика обработки будет разной.

> их обрабатывает 1 участок весьма компактного универсального кода, а не 1000

Этот один участок универсального кода должен выглядеть тогда как eval(x).

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

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

Я, если честно, смутно представляю необходимость в разной логике в таких объемах. Обычно в редаксе экшо́ны да редюсеры отличаются парой констант, а остальные 100 строк пишутся обычной копипастой. Дан Амбрамов, как истинный бог говнокодеров считает такой подход правильным
DRY is not free. You pay with a deeper abstraction stack and a higher price for changing individual cases later. I love boring code now.

Ох. Какой кошмар :) Впрочем, может быть в конторах вроде Facebook это и правда оправдано.

На сколько я знаю, в Фейсбуке Редаксом не пользуются, а Абрамов — просто рядовой разработчик. Эту идеологию он продвигает не в своей компании, а в среде фанатов.
В каждом случае логика обработки будет разной.

Мне кажется, что у вас просто вселенский бардак в кодовой базе. Оттого и кажется, что логика столь вариативна, что DRY идёт лесом. Либо передоз какой-то технологии, в которой на каждый чих создаётся новая сущность (java?).

> Мне кажется, что у вас просто вселенский бардак в кодовой базе. Оттого и кажется, что логика столь вариативна, что DRY идёт лесом.

Ну если согласно требованиям в ответ на одно действие надо сделать Х, а в ответ на другое Y — то логика разная, чего же тут поделаешь? Да, как я уже выше говорил — все можно обобщить, в пределе до eval. Штука в том, что редакс тогда нафиг не нужен, он просто дает бесполезную лишнюю индирекцию. Смысл в редаксе есть лишь до тех пор, пока обобщение отсутствует.
в редьюсерах получается одна огромная портянка на кучу ифов

Зачем там куча if-ов? Серьёзно. Есть же целая куча DRY паттернов работы с кодом на JS.


Ну она и есть обычно кастомная

95% кода сложных форм это обрыдлая копипаста. Отличаются справочники, пути к серверу, конфигурация валидации и прочая мелочь. Действительно кастомной логики не более 5%. Уж тем более в этом кровавом ынтерпрайзе.


В редьюсерах только самая тривиальная логика. Мозг — асинхронные экшоны.

оО. WAT? Вы знаете… Я пасс. После такого я не готов дальше дискутировать. Вы меня нокаутировали.

В редьюсерах только самая тривиальная логика. Мозг — асинхронные экшоны.

И этот человек запрещал мне писать логику в мидлварях!

Обычно редьюсер определяет тип экшона (и логику совю) по полю type. Вы предлагаете просто вместо поля type использовать другое поле. У вас не стал тут «один экшон», вы просто переместили квалификатор типа из одного поля в другое. Логика для каждого типа все равно в редьюсере будет своя. И все равно ее трудно будет отслеживать.
Так в экшне редьюсеру должно придти только новое значение для поля, делать он будет одно и то же. Выглядеть редьюсер будет примерно так:
function(state, action){
  return {
    ...state,
    madFormFields: {
      ...state.madFormFields,
      [action.fieldName]: action.fieldValue
    }
  }
}
> Так в экшне редьюсеру должно придти только новое значение для поля, делать он будет одно и то же.

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

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

Кажется я понял вас. Просто если скажем на форме расположено 25 <input/>-ов, то у меня это будет 1 action

Конечно когда у вас 25 однообразных инпутов все просто и можно запилить 1 action. А теперь представьте что за изменением каждого из инпутов стоит совершенно разная бизнес логика. Что делать?
Что делать?

Никогда не встречал форм, где было бы 25 input-ов, и все они имели своё собственное поведение. А уж тем более в тех проектах, которые вы описали выше. Они там в 99% случаев одинаковые до тошноты. Как и сами проекты, ИМХО. Можно скриншот такой формы? :) Я боюсь, что типичный пользователь ПК, встретив такой цирк, с визком и ором закроет вкладку и уйдёт к конкуренту. Если юзер подневольный, ждите падения производительности. Он просто запутается.

> Я боюсь, что типичный пользователь ПК, встретив такой цирк, с визком и ором закроет вкладку и уйдёт к конкуренту.

Мы же говорили о сложных интерфейсах (к каковым, конечно, ни лента твиттов ни счетчик числа сообщений не относятся, по понятным причинам).

Интерфейс — такая штука, которая занимается взаимодействием с пользователем (ввод-вывод информации). Сложный интерфейс = сложное взаимодействие с пользователем (вывод большого количества «сложных» данных, либо ввод большого количества данных с нелинейным flow).

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

> Если юзер подневольный, ждите падения производительности.

Падение производительности по сравнению с чем? С отсутствием решения задачи? :)

Мне кажется вы называете сложным, то, что является простым. И не просто простым, а более чем формализуемым. Я этих форм уже наклепался. И с autocomplete, и с валидацией, и связанных с другими полями сложным образом, и перевычисляемых на основе изменения других данных и мн. другое. Нет в этом никакой магии. И не нужно для этого 1000 action-ов. Разве что если вы ну очень большую любовь питаете к action-ам. А ещё, вы похоже, любите преувеличивать. В каждом вашем сообщении у меня складывается такое ощущение. Особенно когда вижу фразу "по-разному".


Падение производительности по сравнению с чем? С отсутствием решения задачи? :)

По сравнению с тем, когда контролы выглядят одинаково, работают идентично, ввиду того, что построены на одной кодовой базе. Это касается и валидации, и справочников, и всяких хинтов, и различного интерактива вроде стадий, скрытия/показа блоков, авто-вычисляемых полей. Если у вас всякий раз для этого пишется новый код, вместо DRY, то боюсь, что на выходе вы получаете такой цирк, что бедные ваши пользователи. Либо вы очень любите копи-пасту, особенно её рефакторинг.

> И не нужно для этого 1000 action-ов.

Конечно же, не нужно. Просто делаете, как выше указано, один экшен с редьюсером вида x => return x.payload. А потом к хренам выкидываете редакс :)

Неужели вы думаете, что кто-то в здравом уме и твердой памяти пишет по 1000 экшонов? Конечно нет. Просто стейт выносится из редакса.

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

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

> Мне кажется вы называете сложным, то, что является простым.

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

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

Так представление разное (информация-то разная).
А что не так с разной бизнес-логикой? Какая-то религия мешает проверять в редусерах или мидлварях не только свойство type, но еще и какое-нибудь field?
А что не так с SRP? Давайте лучше DRY вспомним…
Бизнес-логика в миддлеварях — это лучшее, что я слышал, вы сейчас сделали мой день.
У меня сейчас под носом довольно сложное приложение. Ради интереса посмотрел, frontend-js файлы: 37k lines, 425 files

На самом деле это всего-лишь иллюзия сложно, создаваемая использованием Редакса. Если убрать всю копипасту, вносимую гнилыми подходами этой библиотеки, то там бы осталось от силы 5k строк кода. Посмотрите сами свои экшены и редюсеры незамутненным взглядом и скажите, сколько файлов отличаются между собой только названиями констант и лишь мелкими деталями в две строчки.

Ну не 5k, 10-15 было бы. Просто потому, что я очень сильно озаботился тем, чтобы всё было компактным. Скажем у меня нет эти констант, про которые вы пишете (они автоматически генерируются). Файлов очень похожих друг на друга тоже 0. Я очень не люблю копи-пасту. Так что раздутость кода обусловлена тем, что action-ы таки писать приходится, и то, что immutable-code очень громоздкий и малопонятный. Вот именно за счёт reducer-ов я мог бы ужать десяток тысяч. А ну и propTypes (я пока не использую TS).


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

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

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

Если я выкину Redux, то я потеряю помимо его недостатков, ещё и все его преимущества. Разве это не очевидно?

А какие преимущества редакс? Опишите, пожалуйста. Я просто особых не заметил, пока полгода с ним писал. Может лучше подыскать нормальные инструменты без его недостатков? MobX тот же

Не имею ничего против MobX. И хочу попробовать в каких-нибудь будущих проектах. В сравнении с Knockout я уже писал неоднократно. Redux для меня как глоток свежего воздуха. Особенно мне нравится, что я могу получить от пользователя store с последними 50 действиями в redo-undo стеке, списком воспроиведённых action-ов и отловить даже самый сложный баг за конечное время. Что столкнувшись с чем-то очень необычным, я зная, что тут почти всё детерминировано до мелочей, едва ли встречу какой-нибудь плавающий баг или нестандартное поведение. Преимущества выражаются именно на большой кодовой базе, когда начинаешь забывать всё. Ну и тестировать одно удовольствие.

Простите, на 10к строк кода — далеко не большая кодовая база. И как раз на большой кодовой базе значительно сильнее бьют его недостатки. Ведь сложность кода с его количеством растет геометрически, а не линейно, следовательно гнилое поощрение редаксом копипасты сказывается на коде геометрически плохо. На сколько я помню, в редаксе undo нету, только redo до необходимого шага с инишиал стейта, а это, как и все остальное вами описанное, давно делается в OOP паттерном Команда (в нем, правда и undo легко добавляется)
На сколько я помню, в редаксе undo нету

У меня есть. Такие вещи именно на redux реализуются более чем элементарно. В моём случае это довольно сложный редактор, там redo-undo это прямо кнопки на панели.


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


Ну и никто не будет её реализовывать просто так (без необходимости). В redux это просто на сдачу даётся. Скажем redux dev tools умеет redo-undo просто автоматически.


По поводу 10k — ну сколько успел за 12 месяцев написать, столько успел. Sorry что не оправдал ожиданий :) До этого был как раз Knockout.

На самом деле, то что у вас redo-undo так хорошо удалось сделать — это тоже удача ваш опыт.

В redux никто не застрахован от того что в сторе окажется нечто, что не следует восстанавливать после undo или redo. Или наоборот, то, что следует восстанавливать — окажется засунуто в локальный стейт компонента. А то и вообще часть состояния может оказаться на бакэнде…

Я столкнулся с этими проблемами. Там всё очень просто. Но я думаю тут и без того уже сообщений много. Если интересно — напишу в личку.

Там все очень просто если с самого начала постулировать эти самые undo или redo. А вот чтобы взять чужую программу, и добавить undo и redo в нее — тут никакой redux не поможет.

С этим соглашусь. Однако в случае redux приложения это будет сделать в стократ проще, чем в случае knockout приложения. Второе скорее всего придётся просто выкинуть.

Неа, не придется :-) Там все тоже очень просто (если, опять-таки, с самого начала делать с прицелом на undo/redo).


Очень помогает библиотека knockout-mapping. Если завернуть вызов ko.mapping.toJS в ko.computed — получим желанную историю состояний.


А вызов ko.mapping.fromJS поможет восстановить сохраненное состояние.

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

Или наоборот, то, что следует восстанавливать — окажется засунуто в локальный стейт компонента
А вот этого быть не должно изначально. Потому что стейт компонента легко теряется даже без undo/redo (if). И довольно странно видеть вместе глобальный стор, который такие вещи решает, и локальный стейт компонента.

А то и вообще часть состояния может оказаться на бакэнде…
А это интересно. Расскажи поподробнее.
А что подробнее? Гугл-доки видели? :-)
Только что специально проверил. Если открыть документ в новой вкладке, то там не работает undo.
А revision history имеет вообще свой отдельный UI который позволяет сделать предпросмотр и кнопочку «Restore this version».
Так что пример не очень
А вы уверены, что это именно так и должно быть, а не гугл неосилил правильную реализацию? :-)
С точки зрения UX это вполне обоснованное решение. Ctrl+z для локальных недавних изменений, чтобы поправить маленький косячек. Для всего остального — симулируем систему контроля версий.

Но у меня вопрос: если это гугл не осилил, то как должны осилить разработчики средней руки среднего говнопроекта?
Мне тоже интересен этот вопрос :-)

Утверждается что redux в этом как-то поможет...
Очевидно, что на каждую типичную задачу можно найти такие выдроченные условия, что типичное решение не подойдет. Redux позволяет легко ввести типичное undo/redo. Вы же предлагаете сделать undo/redo между разными репликами, в разное время и с условием только частичной подгрузки данных.

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

Ладно-ладно. Это все равно легкая задача, но требует привлечения бекенда.
А вот этого быть не должно изначально. Потому что стейт компонента легко теряется даже без undo/redo (if). И довольно странно видеть вместе глобальный стор, который такие вещи решает, и локальный стейт компонента.

Это, кстати, одна из проблем Редакса. Так можно использовать setState? Или нельзя? Такое впечатление, что фанаты говорят то одну, то другую мысль зависимо от того, что выставит идол в лучшем виде в данном случае. Вот пример, где меня убеждали, что «использовать setState» — это правильный путь при использовании редакса:
habrahabr.ru/post/328152/#comment_10207500
Можно использовать setState. Нельзя использовать setState.

It depends. Выбираете вы. Огребаете вы. Такова идеология, стоящая за redux.

Мне лично просто странно видеть сообщение в духе «никто не гарантирует, что я буду использовать redux для персистентного состояния, поэтому redux не решает проблем с персистентным состоянием». Ну Ыыы же, разве нет?

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

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

  1. Не используйте redux как API внутри универсальных компонентов;
  2. Не используйте setState как API внутри универсальных компонентов;
  3. В Props ожидайте методы, которые читают состояние и которые пишут состояние;
  4. Напишите пару говноврапперов над универсальным компонентом для хипстеров: один с setState, другой с примитивным redux. Второй можно не писать.


Теперь объясню почему у вас вылезла проблема. Redux-way — это сохранять стейт, чтобы он не потерялся. Но у end-developer уже есть свое видение того как должен храниться стейт. Эти видения не могут быть всегда совместимыми.

Проблема интеграции чужих говен модулей/систем вечна. И пока что никто не знает как её решить оптимальным образом.

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

P.S: Мою краткую инструкцию можно распечатать и использовать в качестве туалетной бумаги. Ибо я не пишу универсальные компоненты.
Окей, 1-3 пункты — очевидны.

другой с примитивным redux

И как его написать? В этом же и вопрос. Просто отвечать на вопрос «как написать» — «примитивно напишите» очень странно.

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

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

Либо вы волевым решением навязываете end-user ваше видение того как хранится стейт, либо отдаете это на откуп ему.

Навязывание как правило превращает вашу либу в говно, поэтому вы обречены копаться в говне. Чтобы не копаться в говне — не пишите говна.

И как его написать? В этом же и вопрос. Просто отвечать на вопрос «как написать» — «примитивно напишите» очень странно.
Под «примитивным redux» я подразумевал решение, которое просто работает и которое написано как вам нравится. А если кому-то не нравится как конкретно оно работает, то он может взять и написать под себя используя универсальный компонент.

Впрочем вы можете попытаться экспортировать не просто reducer, а high-order-reducer который можно параметризовать маппингом get/set state. Это в теории должно дать возможность приаттачивать состояние к другой части дерева.

Но тут будет много проблем:
— можно хранить стейт как raw objects, ImmutableJS, seamless immutable, mori — в общем так, как не хочет end-user;
— приаттачивать состояние к другой части стейта может акнуться, потому что другой код может затирать нахер стейт аккордиона.

У меня вопрос: почему вы не любите нормализованный стейт?
Для сравнения, до этого я долго работал с большим приложением на Knockout-е. Я мог несколько дней убить просто, чтобы хотя бы воспроизвести баг, описанный пользователем. Последовательность отработки различных deferred и пр. observable и impure функции приводили порой к настолько сложным ситуациям, что в части случаев приходилось цеплять нелепые костыли (т.к. за разумное время решить проблему не представлялось возможным). И даже вопроизвести некоторые нюансы конкурентной обработки цепочек computed deferred observable на простом примере могло отнять пару часов.

Только что обратил внимание на этот абзац… Не может ли быть, что вы просто неправильно готовили Knockout?


Я почему-то очень часто встречаю подобное в коде коллег: там, где можно было бы использовать один pureComputed и возвращать значение через return — зачем-то делается связка computed + observable.


Хотя с deferred согласен, это в Knockout прямо-таки беда. Не зря в MobX всяческие задержки оставили только в реакциях.

Не может ли быть, что вы просто неправильно готовили Knockout?

Именно так. Правильно готовить Knockout с первого раза может только ну очень продвинутый инженер, имеющий подобный опыт. Я о том и пишу. Что Knockout заставляет строить архитектуру большого приложения самостоятельно. Практически без каких-либо ограничений. Нет, кажется, даже никакого knockout-way. А так как на тот проект я пришёл не с нуля, то мне пришлось поддерживать все те архитектурные ошибки, которые там были заложены. И на тот момент я не понимал их. Спустя 3 года пришло понимание, как нужно было делать по уму.


А redux-react оказался сильно проще. И redux-way расписан в мелочах в тысячах статей.


Отдельно забыл сказать, что работал я начал с ним 4-5 лет назад, и тогда ряда вещей в библиотеке просто не было. К примеру pureComputed-ов вроде не было. Компонент не было. Мало что было. А кодовая база росла, росла.

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

Сейчас из каждого утюга просто идёт пиар-компания Redux и React. Доходит до того, что собеседоваться приходят люди, которые не знают азов JS, но уже что-то "мартышат" на React и хотят большую з\п.

На приведенном примере todo-приложения я не увидел, чем же это проще, чем redux. Такие же вью, такие же модели (action-creator + reducer = model).
На самом деле, модель даже получилась сложнее: TodoItem знает про TodoList (чтобы правильно обработать todoItem.remove()). Если один item начнет входить в несколько списков, логика модели начнет необоснованно усложняться (о эти чудные велосипеды для синхронизации нескольких коллекций в backbone, создания зависимых моделей, аггрегирующих данные из нескольких "первичных" моделей).


А еще редакс решает очень частую проблему: дочернему компоненту могут понадобиться дополнительные данные, которых нет у родителя. С редаксом это решается путем оборачивания ребенка в connect() — прозрачно для родителя.
Как это решается в случае с MVC? Лично у меня это также было одной из основных проблем с (моим) backbone-кодом — каждый раз городил огород либо с жесткой зависимостью от глобального синглтон-инстанса модели, либо пробрасывал дополнительные данные по всему дереву сверху вниз.

> На приведенном примере todo-приложения я не увидел, чем же это проще, чем redux.

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

> Как это решается в случае с MVC?

В зависимости от того какая у вас модель — либо вызов рендера находится в модели, и тогда проблемы нет, потому что модель знает о себе все и у нее даже нет понимания таких вещей, что «вот этот кусок — от дочернего компонента», либо модель предоставляет интерфейс для получения данных виду.
UFO just landed and posted this here
Презентация Флакс — это ведь обычная грязная маркетинговая ложь для глуповатых покупателей? В чем манипуляция? В количестве. НА MVC слайде изображено 7 моделей и 7 вьюшек. А на Flux слайде — одна «модель» и одна вьюшка. Неужели Флакс вводит какую-то магию, которая позволяет обойтись одной вьюшкой? Нет, зато эта ложь выглядит круто и ее можно использовать в статьях) А вот как слайд флакса должен выглядеть на самом деле:

А что не так с этой схемой? Если к ней присмотреться, то всё хорошо. Особенно если вместо Flux взять Redux и 10 сторов объединить в один. Ну и View-и выстроить в древовидном виде. А action-ы в виде списка.


Эти схемы не показывают реальных проблем подхода. Т.к. эти проблемы сложно нарисовать на схеме :)

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

А что не так с этой схемой?

Так именно, что ничего) Как и со схемой MVC. Просто они ничего не показывают. Очевидно, что это просто грязные манипуляции с визуализацией данных от пропагандистов Флакса.

UFO just landed and posted this here
Я почитал комментарии и не могу определить куда бы засунуть этот текст. Поэтому будет комментарий top-level. Половина проблем с redux начинается с того, что люди переносят свое понимание с flux на redux и с MVC на redux.

Где вы описываете бизнес-логику в MVC? Правильный ответ (нынче): в Model. Вики правда считает, что можно и в модели, и в контроллерах. Но судя по всему современные интерпретации MVC подразумевают, что Model — это не только данные и работа с ними, но еще и бизнес-логику (имеется в виду что слои DAL и BAL вместе называются Model).

Где вы описываете бизнес-логику в redux? Правильный ответ: а хуй его знает, мы сами не знаем, разгребайте как хотите, мы будем вас гуру считать. См. Where should my “business logic” go?

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

Но мой маленький опыт мне подсказывает, что бизнес-логику нужно держать отдельно от redux, а любые попытки отклониться от парадигмы «redux просто работает с состоянием» аукаются.
Пойду писать публиковать свой фреймворк для чистых асинхронных action generator'ов в качестве бизнес-логики.
Чистых опционально чистых action generator'ов action-list generator'ов.
И чем ваше решение будет отличаться от десятков разных говен под redux? Их ведь много, один другого краше.

Большинство подобных попыток обладают одной проблемой: они пытаются примешать бизнес-логику к persistence, тогда как в идеале business logic layer должен скрывать детали persistence (сюда включается вариант с делегированием по соглашению).

Но это пока что не вариант. Потому что во многих современных фронтенд-приложениях бизнес-логика тонкая и не переиспользуемая. Поэтому введение четкого BL будет стопроцентным оверхедом. И все долбятся костылями, примешивая её понемногу куда ни попадя.

Выбор как-бы не очень хороший.
Бизнес-логика не только меняет данные, а еще и читает их. Одних action generatorов вам не хватит
Читает — в смысле, из стора? Так давать им getState через middleware.
Если гарантировать что в reducer попадет честный getState, а к компонентам завернутый getState, то может сработать.
Но все еще нужен механизм по комбинированию такого стейта. Я не уверен, что селекторов хватит, хотя и может хватить.
Реакт/редакс это часть presentation слоя. Мы ведь тут HTML странички рисуем. А бизнес-логика должна жить на «сервере». Вот там и поищите. :)

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

Редакс появился как инструмент для решения совершенно конкретной задачи — связного обновления страницы в браузере, с возможностью сериализации изменений (aka time machine).

Притягивать сюда MVC можно, но скорее вредно. Это уже и до нас сделали по сто раз. Теперь каждый может назвать с десяток вариаций этого шаблона. Из-за этого в любой дискуссии про MVC половина времени уходит на синхронизацию, какой-же из вариантов мы рассматриваем.

Articles

Change theme settings