Вам действительно нужен Redux? +38


Не так давно React позиционировал себя как "V in MVC". После этого коммита маркетинговый текст изменился, но суть осталась той же: React отвечает за отображение, разработчик — за все остальное, то есть, говоря в терминах MVC, за Model и Controller.


Одним из решений для управления Model (состоянием) вашего приложения стал Redux. Его появление мотивировано возросшей сложностью frontend-приложений, с которой не способен справиться MVC.


Главный Технический Императив Разработки ПО — управление сложностью

Совершенный код

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


  • состояние всего приложения хранится в одном месте
  • единственный способ изменить состояние — отправка Action'ов
  • все изменения происходят с помощью чистых функций

Смог ли Redux побороть возросшую сложность и было ли с чем бороться?


MVC не масштабируется


Redux вдохновлен Flux'ом — решением от Facebook. Причиной создания Flux, как заявляют разработчики Facebook (видео), была проблема масштабируемости архитектурного шаблона MVC.


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


  1. modelOne изменяет viewOne
  2. viewOne во время своего изменения изменяет modelTwo
  3. modelTwo во время своего изменения изменяет modelThree
  4. modelThree во время своего изменения изменяет viewTwo и viewFour

О проблеме непредсказуемости изменений в MVC также написано в мотивации Redux'a. Картинка ниже иллюстрирует как видят эту проблему разработчики Facebook'а.



Flux, в отличии от описанного MVC, предлагает понятную и стройную модель:


  1. View порождает Action
  2. Action попадает в Dispatcher
  3. Dispatcher обновляет Store
  4. Обновленный Store оповещает View об изменении
  5. View перерисовывается


Кроме того, используя Flux, несколько Views могут подписаться на интересующие их Stores и обновляться только тогда, когда в этих Stores что-нибудь изменится. Такой подход уменьшает количество зависимостей и упрощает разработку.



Реализация MVC от Facebook полностью отличается от оригинального MVC, который был широко распространен в Smalltalk-мире. Это отличие и является основной причиной заявления "MVC не масштабируется".


Назад в восьмидесятые


MVC — это основной подход к разработке пользовательских интерфейсов в Smalltalk-80. Как Flux и Redux, MVC создавался для уменьшения сложности ПО и ускорения разработки. Я приведу краткое описание основных принципов MVC-подхода, более детальный обзор можно почитать здесь и здесь.


Ответственности MVC-сущностей:


  • Model — это центральная сущность, которая моделирует реальный мир и бизнес-логику, предоставляет информацию о своем состоянии, а также изменяет свое состояние по запросу из Controller'a
  • View получает информацию о состоянии Model и отображает ее пользователю
  • Controller отслеживает движение мыши, нажатие на кнопки мыши и клавиатуры и обрабатывает их, изменяя View или Model

А теперь то, что упустил Facebook, реализуя MVC — связи между этими сущностями:


  • View может быть связана только с одним Сontroller'ом
  • Сontroller может быть связан только с одним View
  • Model ничего не знает о View и Controller и не может их изменять
  • View и Controller подписываются на Model
  • Одна пара View и Controller'а может быть подписана только на одну Model
  • Model может иметь много подписчиков и оповещает всех их после изменения своего состояния

Посмотрите на изображение ниже. Стрелки, направленные от Model к Controller'у и View — это не попытки изменить их состояние, а оповещения об изменениях в Model.



Оригинальный MVC совершенно не похож на реализацию Facebook'a, в которой View может изменять множество Model, Model может изменять множество View, а Controller не образует тесную связь один-к-одному с View. Более того, Flux — это MVC, в котором роль Model играют Dispatcher и Store, а вместо вызова методов происходит отправка Action'ов.


React через призму MVC


Давайте посмотрим на код простого React-компонента:


class ExampleButton extends React.Component {
  render() { return (
    <button onClick={() => console.log("clicked!")}>
        Click Me!
    </button>
  ); }
}

А теперь еще раз обратимся к описанию Controller'a в оригинальном MVC:


Controller отслеживает движение мыши, нажатие на кнопки мыши и клавиатуры и обрабатывает их, изменяя View или Model

Сontroller может быть связан только с одним View

Заметили, как Controller проник во View на третьей строке компонента? Вот он:


onClick={() => console.log("clicked!")}

Это идеальный Controller, который полностью удовлетворяет своему описанию. JavaScript сделал нашу жизнь легче, убрав необходимость самостоятельно отслеживать положение мыши и координаты в которых произошло нажатие. Наши React-компоненты превратились не просто во View, а в тесно связанные пары View-Controller.


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


Готовим MVC


Для удобства работы с React-компонентами, создадим свой класс BaseView, который будет подписываться на переданную в props Model:


// src/Base/BaseView.tsx
import * as React from "react";
import BaseModel from "./BaseModel";

export default class <Model extends BaseModel, Props> extends React.Component<Props & {model: Model}, {}> {
    protected model: Model;

    constructor(props: any) {
        super(props);
        this.model = props.model
    }

    componentWillMount() { this.model.subscribe(this); }

    componentWillUnmount() { this.model.unsubscribe(this); }
}

В этой реализации атрибут state всегда является пустым объектом, потому что мне он показался бесполезным. View может хранить свое состояние непосредственно в атрибутах экземпляра класса и при необходимости вызывать this.forceUpdate(), чтобы перерисовать себя. Возможно, такое решение является не самым лучшим, но его легко изменить, и оно не влияет на суть статьи.


Теперь реализуем класс BaseModel, который предоставляет возможность подписаться на себя, отписаться от себя, а также оповестить всех подписчиков об изменении состояния:


// src/Base/BaseModel.ts
export default class {
    protected views: React.Component[] = [];

    subscribe(view: React.Component) {
        this.views.push(view);
        view.forceUpdate();
    }

    unsubscribe(view: React.Component) {
        this.views = this.views.filter((item: React.Component) => item !== view);
    }

    protected updateViews() {
        this.views.forEach((view: React.Component) => view.forceUpdate())
    }
}

Я реализую всем известный TodoMVC с урезанным функционалом, весь код можно посмотреть на Github.


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


// src/TodoList/TodoListModel.ts
import BaseModel from "../Base/BaseModel";
import TodoItemModel from "../TodoItem/TodoItemModel";

export default class extends BaseModel {
    private allItems: TodoItemModel[] = [];
    private mode: string = "all";

    constructor(items: string[]) {
        super();
        items.forEach((text: string) => this.addTodo(text));
    }

    addTodo(text: string) {
        this.allItems.push(new TodoItemModel(this.allItems.length, text, this));
        this.updateViews();
    }

    removeTodo(todo: TodoItemModel) {
        this.allItems = this.allItems.filter((item: TodoItemModel) => item !== todo);
        this.updateViews();
    }

    todoUpdated() { this.updateViews(); }

    showAll() { this.mode = "all"; this.updateViews(); }

    showOnlyActive() { this.mode = "active"; this.updateViews(); }

    showOnlyCompleted() { this.mode = "completed"; this.updateViews(); }

    get shownItems() {
        if (this.mode === "active") { return this.onlyActiveItems; }
        if (this.mode === "completed") { return this.onlyCompletedItems; }
        return this.allItems; 
    }

    get onlyActiveItems() {
        return this.allItems.filter((item: TodoItemModel) => item.isActive());
    }

    get onlyCompletedItems() {
        return this.allItems.filter((item: TodoItemModel) => item.isCompleted());
    }
}

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


// src/TodoItem/TodoItemModel.ts
import BaseModel from "../Base/BaseModel";
import TodoListModel from "../TodoList/TodoListModel";

export default class extends BaseModel {
    private completed: boolean = false;
    private todoList?: TodoListModel;
    id: number;
    text: string = "";

    constructor(id: number, text: string, todoList?: TodoListModel) {
        super();
        this.id = id;
        this.text = text;
        this.todoList = todoList;
    }

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

    isActive() { return !this.completed; }

    isCompleted() { return this.completed; }

    remove() { this.todoList && this.todoList.removeTodo(this) }
}

К получившимся моделям можно добавлять любое количество View, которые будут обновляться сразу после изменений в Model. Добавим View для создания новой задачи:


// src/TodoList/TodoListInputView.tsx
import * as React from "react";
import BaseView from "../Base/BaseView";
import TodoListModel from "./TodoListModel";

export default class extends BaseView<TodoListModel, {}> {
    render() { return (
        <input 
            type="text"
            className="new-todo" 
            placeholder="What needs to be done?"
            onKeyDown={(e: any) => {
                const enterPressed = e.which === 13;
                if (enterPressed) { 
                    this.model.addTodo(e.target.value);
                    e.target.value = "";
                }
            }}
        />
    ); }
}

Зайдя в такой View, мы сразу видим, как Controller (props onKeyDown) взаимодействует с Model и View, и какая конкретно Model используется. Нам не нужно отслеживать всю цепочку передачи props'ов от компонента к компоненту, что уменьшает когнитивную нагрузку.


Реализуем еще один View для модели TodoListModel, который будет отображать список задач:


// src/TodoList/TodoListView.tsx
import * as React from "react";
import BaseView from "../Base/BaseView";
import TodoListModel from "./TodoListModel";
import TodoItemModel from "../TodoItem/TodoItemModel";
import TodoItemView from "../TodoItem/TodoItemView";

export default class extends BaseView<TodoListModel, {}> {
    render() { return (
        <ul className="todo-list">
            {this.model.shownItems.map((item: TodoItemModel) => <TodoItemView model={item} key={item.id}/>)}
        </ul>
    ); }
}

И создадим View для отображения одной задачи, который будет работать с моделью TodoItemModel:


// src/TodoItem/TodoItemView.jsx
import * as React from "react";
import BaseView from "../Base/BaseView";
import TodoItemModel from "./TodoItemModel";

export default class extends BaseView<TodoItemModel, {}> {
    render() { return (
        <li className={this.model.isCompleted() ? "completed" : ""}>
            <div className="view">
                <input
                    type="checkbox"
                    className="toggle"
                    checked={this.model.isCompleted()}
                    onChange={() => this.model.switchStatus()}
                />
                <label>{this.model.text}</label>
                <button className="destroy" onClick={() => this.model.remove()}/>
            </div>
        </li>
    ); }
}

TodoMVC готов. Мы использовали только собственные абстракции, которые заняли меньше 60 строк кода. Мы работали в один момент времени с двумя движущимися частями: Model и View, что снизило когнитивную нагрузку. Мы также не столкнулись с проблемой отслеживания функций через props'ы, которая быстро превращается в ад. А еще нам не пришлось создавать фэйковые Container-компоненты.


Что не так с Redux?


Меня удивило, что найти истории с негативным опытом использования Redux проблематично, ведь даже автор библиотеки говорит, что Redux подходит не для всех приложений. Если ваше frontend-приложения должно:


  • уметь сохранять свое состояние в local storage и стартовать, используя сохраненное состояние
  • уметь заполнять свое состояние на сервере и передавать его клиенту внутри HTML
  • передавать Action'ы по сети
  • поддерживать undo состояния приложения

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


Redux слишком сложный, и я говорю не про количество строк кода в репозитории библиотеки, а про те подходы к разработке ПО, которые он проповедует. Redux возводит indirection в абсолют, предлагая начинать разработку приложения с одних лишь Presentation Components и передавать все, включая Action'ы для изменения State, через props. Большое количество indirection'ов в одном месте делает код сложным. А создание переиспользуемых и настраиваемых компонентов в начале разработки приводит к преждевременному обобщению, которое делает код еще более сложным для понимания и модификации.


Для демонстрации indirection'ов можно посмотреть на такой же TodoMVC, который расположен в официальном репозитории Redux. Какие изменения в State приложения произойдут при вызове callback'а onSave, и в каком случае они произойдут?


При отсутствии желания устраивать расследование самостоятельно, можно заглянуть под спойлер
  1. hadleSave из TodoItem передается как props onSave в TodoTextInput
  2. onSave вызывается при нажатии Enter или, если не передан props newTodo, на действие onBlur
  3. hadleSave вызывает props deleteTodo, если заметка изменилась на пустую строку, или props editTodo в ином случае
  4. props'ы deleteTodo и editTodo попадают в TodoItem из MainSection
  5. MainSection просто проксирует полученные props'ы deleteTodo и editTodo в TodoItem
  6. props'ы в MainSection попадают из контейнера App с помощью bindActionCreator, а значит являются диспетчеризацией action'ов из src/actions/index.js, которые обрабатываются в src/reducers/todos.js

И это простой случай, потому что callback'и, полученные из props'ов, оборачивались в дополнительную функциональность только 2 раза. В реальном приложении можно столкнуться с ситуацией, когда таких изменений гораздо больше.


При использовании оригинального MVC, понимать, что происходит с моделью приложения гораздо проще. Такое же изменение заметки не содержит ненужных indirection'ов и инкапсулирует всю логику изменения в модели, а не размазывает ее по компонентам.


Создание Flux и Redux было мотивировано немасштабируемостью MVC, но эта проблема исчезает, если применять оригинальный MVC. Redux пытается сделать изменение состояния приложения предсказуемым, но водопад из callback'ов в props'ах не только не способствует этому, но и приближает вас к потере контроля над вашим приложением. Возросшей сложности frontend-приложений, о которой говорят авторы Flux и Redux, не было. Было лишь неправильное использование подхода к разработке. Facebook сам создал проблему и сам же героически ее решил, объявив на весь мир о "новом" подходе к разработке. Большая часть frontend-сообщества последовала за Facebook, ведь мы привыкли доверять авторитетам. Но может настало время остановиться на мгновение, сделать глубокий вдох, отбросить хайп и дать оригинальному MVC еще один шанс?


UPD


Изменил изначальные view.setState({}) на view.forceUpdate(). Спасибо, kahi4.

Вы можете помочь и перевести немного средств на развитие сайта



Комментарии (432):

  1. kahi4
    /#10706112 / +1

    У вас получился 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?

    • Telichkin
      /#10706138

      У вас получился 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?

      А зачем он нужен модели?

      • kahi4
        /#10706168 / +1

        А зачем он нужен модели?

        У baseView, ошибся. Нужен, если модель поменяли, привязаться к новой.


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


        Про modelUpdated у базовой вью тоже не совсем верно. Так уж сложилось, что в реакте для этого принято использовать High ordered component, по типу редуксовского коннекта, observer из mobx-react и подобного. Почему? Реактивность, все дела, вы не вмешиваетесь в стандартный жизненный цикл компонента и все прочее.


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


        Если говорить идеологически то а: смалталк не единственный канон MVC, обычно распространено все таки явление что контроллер и вью развязаны, на это есть причины (хотя бы посмотрите где этот смалталк сейчас, а сколько реализаций с более привычным подходом)?


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


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


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


        P.s. Я не защищаю редукс, он мне жутко не нравится, лично я бы смотрел в сторону cyclejs, но там тоже не все гладко.

        • Telichkin
          /#10706278

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

          • kahi4
            /#10706314

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

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


            По поводу реакта как вью — тут вопрос сильно глубже. В данном примере можно докопаться до прямого вызова методов, но вот что больше интересно — как вы решите сопутствующие проблемы в виде роутинга? Да и вообще обобщить: кто порождает модели? Кто делает асинхронные запросы? Я так понял, за все отвечают модели, реакт только отображение. Хорошо, не спорю, поддерживаю всеми руками.


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


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


            Теперь про пример с фейсбуком. Дело в том, что помимо Chat есть так же SideBar, который так же должен отобразить эту цифру, и есть шапка — в итоге три места. Нужно либо шарить между ними Chat, либо искать другой метод. В ангуларе, шарпе, Джаве и всем здоровом мире используют DI для этого, но в реакте вот религия не позволяет. На самом деле, проблемы то и заключаются в том, когда разные элементы страницы начинают между собой общаться событиями, возникает вопрос кто кого должен порождать, кто кого передавать и все такое. Проблемы начинаются именно тогда, когда зависимости асинхронны, например что-то внизу должно среагировать на то, что что-то вверху изменилось, да ещё и подтянуть актуальные данные. Модели начинают знать слишком много друг о друге, им нужно подписываться друг на друга. Это попытались решишь через однонаправленный поток событий и возможность любому компоненту выловить любое событие. Потом кто-то насмотрелся на хаскель, захотел чистых функций и все такое и создал редукс. Иммутабельнотсь стейта скорее техническое ограничение, чем прихоть. От идеального MV* проблема все равно не решится.


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


            Mobx — да, прям советую. У него тоже не без проблем, но фактически он просто уберёт с вас будущие проблемы с подпиской/отпиской и всем таким.

            • Telichkin
              /#10706452

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


              Интересная беседа получается, спасибо за нее.

              • kahi4
                /#10706484

                Могу сразу сказать, что при таком подходе вам придётся таскать сверху вниз огромную кучу моделей. Спор как это решить действительно идёт с 80х, причём как в C++, C#, так вон и в фронте, потому что у каждого решения есть свои недостатки (поправьте если я не прав и есть прям золотое универсальное решение).


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


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


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


                Есть запрос /project, по которому рисуется весь каркас приложения, шапка и прочее. Есть запрос /data, по которому рисуются данные в табличке. Пока все нормально. А теперь есть кнопка «подключить источник данных», которая сперва отправляет запрос на /add-source, рисует иконку загрузки в шапке и в таблице, когда он выполнился, начинает опрашивать каждые 10 секунд проект на предмет изменения флага, после чего идёт обновлять данные и только после этого убирает иконку загрузки и показывает реальные данные на своих местах. Нажать кнопку можно в двух местах. Начнём с этого. В действительности все гораздо сложнее, например пользователь за это время может банально переключить на другое представление и впустую ходить за данными смысла нет, ещё проекты могут измениться на сервере независимо, их вообще могут сбросить, самих кнопок три группы по 6, при этом данные берутся из большего количества источников и мне кажется нам пора переделывать API (злой комментарий про то, что на бэкэнде сделают как им удобнее, а потом «че вы так все переусложняете»).


                P.S. Я использую rx и счастлив, в целом то. Там такое делается просто, но есть другие проблемы.

                • VolCh
                  /#10706662

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

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

                  • mayorovp
                    /#10706670

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

                    • VolCh
                      /#10706816 / +1

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

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

                      • Druu
                        /#10706956

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

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

                • vintage
                  /#10708952

                  Я использую 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) ,
                  )

                  • mayorovp
                    /#10708978

                    Кажется, вы пытаетесь сделать $mol из rx — отсюда и такие «типичные» костыли…

                  • Druu
                    /#10709276

                    > Не знаю что делает shareReply, но Rx надо использовать по минимуму, тогда не будет этих проблем.

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

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

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

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

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

                    • vintage
                      /#10710546 / -1

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


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

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


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

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


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

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

                      • mayorovp
                        /#10710582

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

                      • Druu
                        /#10710608

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

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

      • DexterHD
        /#10706180 / +2

        Уважаемый автор, рискну предположить что на вас здесь не поймут, ибо вы затрагиваете вопросы религии. :) Я поясню о чем я:

        Проблема в том что в мире современной разработки в среднем по индустрии возраст разработчика 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.

        • token
          /#10706184 / +1

          Подписываюсь под каждым вашим словом.

          • kahi4
            /#10706222 / +1

            Я так понимаю это обо мне? Просто я идеально подхожу — 25 лет, опыт программирования лет 7 всего, все дела.


            Ну во-первых, почему люди, не владеющие и не набившие кучу шишок в реакте учат сообщество как этим реактор пользоваться? Разумеется в ответ слышат критику, а потом начинают «да во фронтэнд понабрали хипстоты». (Это е относится к автору, критику он вроде слушает, а не спускается до авторитета возврата)


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


            В-третьих не знаю как там в 80х, в 2018 принято по MVC считать семейство паттернов, включающих MVP, MVVM и ещё с десяток других (в статье, к слову, описан MVP). И несмотря на то, что это даже на хабре обсосано со всех сторон, некомпетентными являются молодые. Потому что не путаются в современной терминологии, я полагаю.


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


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


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

            • token
              /#10706232 / +1

              Промахнулись с комментарием?

            • DexterHD
              /#10706284

              Я так понимаю это обо мне? Просто я идеально подхожу — 25 лет, опыт программирования лет 7 всего, все дела.

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

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

              Потому что React это всего лишь библиотека, потому что есть люди которые знают фундамент и понимают принципы работы всего того что лежит под капотом React, а так же под капотом всех внешних слоев, им не нужно набивать шишки тыкаясь в StackOwerflow и в документацию чтобы понять проблемы и суть вещей, они эти проблемы решали до появления React и прочих библиотек и фреймворков. Часть из этих людей набили кучу шишок еще тогда когда Фейсбука не было в проекте, другая часть посвятила всю свою жизнь (десятки лет) каким то фундаментальным проблемам в области разработки в той или иной отрасли. И для меня лично странно почему многие в упор отказываются перенимать этот опыт, вместо этого поддаваясь религии и мнению большинства (сообщества). JS сообщество переживает тот же кризис что в середине 2000 переживало PHP сообщество (Там тоже поначалу все изобретали сами не учась у предыдущих поколений. Хорошо что сейчас уже тенденция пошла в правильное русло, и есть реально грамотные разработчики и качественные продукты Enterprise уровня). Не переживайте так. Такое случается с любым не окрепшим организмом.

              Это я к тому, что… что за предвзятость на хабре? Это, как минимум, не профессионально.

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

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

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

              • Atorian
                /#10706328

                Кто не помнит своего прошлого, обречен пережить его вновь.
                — Джордж Сантаяна

                Я наблюдаю именно то что вы описали. А вы говорите то же что и Дядька Боб.

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

                Вот что точно не профессионально — игнорировать опыт старших коллег(по опыту) и самонадеянно пилить свои велосипеды

              • kahi4
                /#10706388

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


                Ну а в остальном я в чем-то с вами согласен, в чем-то нет. Фронтов на рынке хороших найти сложно, это просто объективный факт. Но вот про реакт и шишки — не согласен. Дед Вася, который 50 лет ремонтировал жигули и знает все как работает под копотом вряд ли так снаскоку разберётся в современном автомобиле. Это я к тому, что реакт довольно специфично работает под капотом и вряд ли virtual dom — старое и хорошо забытое. Что-то похожее — да, но общих идей как это могло быть устроено, не всегда достаточно.


                Ну а про молодость и кризис фронта — я не знаю что сказать. Повзрослеет и станет интерпрайзом, а сейчас молодая инфраструктура? Джава просто синоним слова интерпрайз, только что-то сейчас просто в безумном количестве тянет чужих фишечек, а гредл с мавеном далеки от идеала. И развивается она довольно активно, вон даже Котлины всякие выходит. Все нормально с js, наконец сложилась определенная инфраструктура, куча инструментов и возможностей. Вот то что на собеседовании заявляют «кому нужны эти прототипы, в es6 class написал и все» — это да. С тем что встречаются (не буду говорить про большинство, но очень много) люди, у которых ноль идей о том как это работает под капотом — я не буду спорить, даже наоборот поддержу. Но ни реакт, ни жс тут ни при чем. И думаю, это просто фронты почему то стали просто стереотипом малой компьютерной грамотности, хотя на собеседованиях на ту же джаву не все могут сказать чем отличается интерфейс от абстрактного класса.


                Ну а почему тащат редукс и реакт везде? На это я знаю ответ и отвечу просто. Вакансии посмотрите — в 9 из 10 стоят эти два слова. Конечно же начинающие смотрят на то, что востребовано и пытаются учить, таща его куда только можно. Плохо это? Если бы они при этом учили что как и почему — нет. Но не учат. Плохо то, что эм мои сверстники не могут сказать где хранится переменная: в стеке или в куче (если вообще знают что это и зачем это), на какой черт нужен callee в js, или почему в usb можно подключить только 127 устройств (включая хабы)? Наверное да, но с другой стороны они что-то программируют и даже многие зарабатывают больше чем те, кто программирует уже лет 30.

                • Atorian
                  /#10706460

                  Проехали. У каждого своя каша в голове. Давайте к конструктиву.

                  Мне вот например статья зашла. Пример пусть и простой, но похож на то, что я хочу сделать для своих проектов на реакте и когда-то давно делал для AS3 во флэше, и на джаве. А вот почему это мне нравится — это уже другой вопрос.

                  Я искренне уверен, что код я пишу для людей, а не для интерпретатора. Так что, я хочу чтобы он был прост и понятен любому новоприбывшему на проект. С редаксом так не выходит. А вот PureMVC — работает на ура. При том что оно существует ОЧЕНЬ давно. Реакта тогда и в помине небыло. И этот фреймворк решает проблему на всех платформах до сих пор. Это развитая идея того самого MVC, упомянутого автором статьи. Эта модель гораздо ближе человеку и легче понимается, чем эти все рекурсивные вызовы и потоки.

                  Другим ключевым моментом, для меня, является тестопригодность приложения.
                  PureMVC созданный черти знает когда, хорошо ложиться в описаную Р. Мартином «Чистую Архитектуру». По сути просто имплементирует DiP.
                  А это значит, что мне не надо тащить в проект дополнительно 3-5 сложных фреймворка, чтобы протестировать все. Достаточно будет простого xunit фреймворка.

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

                  Не надо принижать достоинство и авторитет старшего поколения. Те люди, у которых надо учиться — успешно выполняют вашу работу и сейчас. А опыта у них на лет на 20 больше.

                  Редукс тащат всюду по тому, что об этом только хабр и поет. В том смысле, что актуальные новости на IT ресурсах — они именно про то что сейчас актуально. А проблема MVC была актуальна 20-30 лет назад. Новички смотрят главную страницу хаба, а там про MVC ни слова. Большая часть этих молодых людей в тематических вузах не училась, фундаментальных знаний не получала. Хорошие книжки слишком толстые, люди боятся их. А в тонких — ничего нет.
                  Старшим не верят.

                  Про «фронты» можно долго беседовать, но это из разряда холиваров. Я не верю что можно переубедить кого-либо в этой битве написав коммент. А стереотипы не на пустом месте рождаются.

                  • kahi4
                    /#10706500

                    Ещё года три назад каждая третья статья на реакте была про MVC, а теперь вдруг в нем все проблемы решились? Нет, просто большинство переключилось на другие вещи. Да и вообще — прогресс есть прогресс, то что мы видим и обсуждаем сейчас не более чем развитие того что было. Или вот MVC — канон и идеал? Притом что я не отрицаю, а наоборот утверждаю что вообще весь фронт — это только V в целом приложении и отдельно жить не может.


                    Далее, если вы будете писать pureMVC на фронте без использования библиотек — рано или поздно по мере усложнения приложения напишите свой велосипед, только наткнётесь ещё на специфичные для фронта проблемы, которых не было у других. Следует сразу взять например бэкбон. Замечательная, простая и понятная библиотека, всем рекомендую. Компонентный подход там кривоват, но все же. А потом дом медленный, да и не на пустом месте появились реакты и ангулары. Это не более чем развитие технологий и ничего в них плохого нет.


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


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


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


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

                    • Druu
                      /#10706648

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

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

                      • Chamie
                        /#10706658

                        Погодите, как экшны могут что-то «спавнить»? Экшн — это объект с данными, который отправляется диспетчеру. Вы про action creator'ы, может быть?

                        • Druu
                          /#10706674 / -1

                          > Экшн — это объект с данными, который отправляется диспетчеру.

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

                          • Chamie
                            /#10707300 / +1

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

                • Druu
                  /#10706644 / +1

                  > Это я к тому, что реакт довольно специфично работает под капотом и вряд ли virtual dom — старое и хорошо забытое.

                  virtual dom — это обычный буфер. Понятию буфера (а так же принципам работы с буферами того или иного вида того или иного назначения) не один (и не два и даже не три) десятка лет. Более того — операции с домом буферизовывались задолго (очень задолго) до того как фейсбук вообще появился.

            • DexterHD
              /#10706292

              Это я к тому, что… что за предвзятость на хабре? Это, как минимум, не профессионально

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

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

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

        • kuftachev
          /#10706482

          Согласен на 100%

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

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

          P.S. На днях опросил 4 человек и 2 из них не знали что такое кэш-промахи, 3 сеньор и один выше (он тоже знал).

        • apapacy
          /#10706496

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

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

          («А теперь перейдем к чему-то более практичному. Предположим Ваш марсолет должен ....»)

          • DexterHD
            /#10707012

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

            • apapacy
              /#10707088

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

              • DexterHD
                /#10707118

                Мне сложно представить какой тестовый проект можно создать

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

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

                martinfowler.com/bliki/BoundedContext.html

                • apapacy
                  /#10707740

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

        • Druu
          /#10706628

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

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

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

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

        • VolCh
          /#10706646 / +1

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

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

          • shir
            /#10707016

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

            • defaultvoice
              /#10708180

              В качестве альтернатив есть final-form (автор – создатель redux-form) и formik. Оба делают жизнь поприятнее

              • shir
                /#10709794

                formik смотрел. Судя по тому что увидел в README это тот же redux-form только без redux'а. API очень похоже. final-form (после очень бегло взгляда) вроде как тоже. Ну т.е. проблемы будут те же самые что и при использовании redux-form. Как минимум в моем случае.


                Тут буквально вчера наткнулся на form-for. Вот это более интересный вариант с другим подходом. Наверное в следующем react проекте буду его пробовать.

                • faiwer
                  /#10709808 / +1

                  Ну т.е. проблемы будут те же самые что и при использовании redux-form

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

                  • shir
                    /#10710568 / +1

                    Для относительно простых форм которые повторяют модель redux-form только помогает и упрощает.


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


                    В другом проекте, я столкнулся с тем что redux-form заточен под работу с Promise при сабмите. А там особенность была в том что promise в action нельзя было передать. Пытался сделать полностью через action'ы, но в этом случае некоторые моменты работали не корректно.

        • saggid
          /#10708322 / -1

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


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


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

        • Frimko
          /#10708746

          зайди на твич, они на react-redux

    • Gentlee
      /#10706148 / +1

      Redux нужно уметь готовить. Он очень простой если иметь один Component.setState-like reducer и dispatch превратить в setState. Tогда и проблем с асинхронностью не возникает, и middleware не нужны. Пример https://github.com/Gentlee/redux-light

      • kahi4
        /#10706178

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

        • Gentlee
          /#10706330

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

          • kahi4
            /#10706402

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

            • Gentlee
              /#10706630

              По поводу mobx — код с ним получится сложнее. Хранение состояния в одном месте выглядит более натурально и многое упрощает.

              По поводу тестирования без инжектов — придется таки добавить redux-thunk и использовать mapDispatchToProps, заставив actions возвращать функции (setState, getState) => { ... }

              • VolCh
                /#10706632

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

        • Druu
          /#10706654

          > Да и вообще зачем вообще тогда редукс нужен?

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

          • VolCh
            /#10706668

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

            • Druu
              /#10706682 / +1

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

              • VolCh
                /#10706820 / +1

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

                • Druu
                  /#10706960

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

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

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

    • stardust_kid
      /#10709860

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

  2. apapacy
    /#10706158

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

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

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

    • Telichkin
      /#10706302

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

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

      • apapacy
        /#10706400

        Наверное потому что там не последнюю роль играет Джереми Ашкеназ (автор backbone.js)

  3. kahi4
    /#10706166

    Не там ответил

  4. Atorian
    /#10706304

    Спасибо за статью. Согласен с автором, про необоснованную сложность редукса. Уже года полтора пытаюсь донести до наших фронтедщиков, что это зло во плоти. Да времени небыло запилить хороший контр пример.
    Хотел уточнить, в примерах, вы специально упростили, слив вместе контроллер и вью? Рассматриваете ли вы подход из PureMVC как более правильный? Где реакт компоненты будут играть роль именно UI компонентов и предоставлять апи, а над ними будет слой ViewController'ов?

    • Telichkin
      /#10706488

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


      View и Controller слил воедино, потому что они всегда идут парой. Когда я осознал, что события JavaScript обрабатывают все мои кейсы, для которых в оригинальном MVC нужен был отдельный Controller, то решил убрать лишнюю сущность. Если вам нужно контролировать более сложные пользовательские input'ы, то без отдельного Controller'а не обойтись.


      Бегло просмотрел лендинг PureMVC, но не успел пока что осознать. Завтра вечером посмотрю внимательней и опишу свои впечатления.

    • Telichkin
      /#10708608

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


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

      • Atorian
        /#10710614

        Спасибо что проявили интерес.

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

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

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

        Т.к. фасад в логике самого приложения не участвует, то вся логика находится в командах. Команды группируются различными образами, можно положить несколько команд в команду-агрегат и т.п. Т.к. команды это отдельные классы, то и хранятся они в разных файлах(в AS3, Java). Соответственно не надо бегать особо по цепочке экшинов, а все наглядно дереве файлов в IDE.

        Команд не может быть меньше чем логических действий в приложении + 1(StartUpCommand). По своей сути эти команды ближе к UseCase из Clean Architecture.

        Разработчики React ведь сами позиционируют его как библиотеку и чисто View. Так что почему не использовать его так, как задумано? Плюсом мы получим Clean Architecture из коробки, ведь если над Реактом будет дополнительный слой, то там можно использовать Presenter, преобразовать все данные и оставить UI компоненты примитивными. А значит проще будет тестировать. Того же, в принципе, можно добиться c Redux. Только в качестве презентеров будут селекторы. Вы можете сами развить идею.

        Безболезненно можно будет перейти на другую UI-библиотеку, но часто ли такой переход осуществляется?

        Вот как раз таки в текущей ситуации, когда фреймворки обновляются каждый год, оно и надо.
        Год назад мне достался проект на ангуляре 1.3. Рабочий бэкенд, не публичный. Конечно же все новые проекты у нас на Реакте. Переписать его весь и сразу — слишком много работы. А если мы поженились с фреймворком, и описали нашу логику в Реакт компонентах или Ангуляр компонентах, по надо именно что переписывать его целиком.
        Слишком затратно и никто этого в здравом уме не будет делать.
        При подходе PureMVC, я мог бы заменять UI компоненты по мере надобности. И выпилить ангуляр со временем.
        Для бизнеса это значит, что не надо нанимать разработчиков для работы на супер старых версиях фремворков, которые никто уже не использует. И не надо раз в 2 года переписывать огромные проекты.
        А для разработчиков — мы можем в разумных пределах пробовать новые библиотеки, заменять обратно не совместимые версии и т.п. Захотелось Vue — сделал страничку отдельную и никто не умер.

  5. LestaD
    /#10706454

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

  6. Legion21
    /#10706486

    По мне так redux довольно прост, в том плане в котором имеет ввиду автор…

  7. Druu
    /#10706624

    > Наши React-компоненты превратились не просто во View, а в тесно связанные пары View-Controller

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

    MVC был придуман _очень_ давно, разделение между контроллером и видом там есть постольку, поскольку первый отвечает за одни устройства (ввод, мышь и клавиатура), а второй за другие (вывод, монитор). При этом в обоих случаях была нетривиальная логика (контроль перекрытия объектов, особенности рендеринга и т.п.) и она четко отделялась.

    На данный момент юзер взаимодействует с интерфейсом, а не с устройствами ввода/вывода, который одновременно является и видом и контроллером (кнопка на веб-странице, например), при этом слой логики на 90% перешел во фреймворк.

  8. Chamie
    /#10706656

    Flux — это MVC, в котором роль Model играют Dispatcher и Store, а вместо вызова методов происходит отправка Action'ов.
    А вас не смущает, что на вашей схеме «правильного» MVC НЕТ «вызова методов»? А есть как раз отправка сообщений (Action'ов).
    Заметили, как Controller проник во View на третьей строке компонента? Вот он:
    Нет, контроллер тут, если уж выделять «контроллер» по вашей схеме — это рантайм браузера, обслуживающий события мыши, плюс экшн-креэйторы, а в третьей строке вы просто запихали контроллерную логику во вью.

    • Telichkin
      /#10706786

      А вас не смущает, что на вашей схеме «правильного» MVC НЕТ «вызова методов»? А есть как раз отправка сообщений (Action'ов).

      Не смущает, потому что отправка сообщения в Smalltalk == вызов метода в современных ОО-языке.


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

      Так и есть, но зачем в текущей задаче и в 90% рядовых задач выносить эту логику в отдельное место? Не поймите меня неправильно, я не против декомпозиции и SRP, но я пришел к выводу, что YAGNI — самый мощный принцип, причем не только в программировании, но и во всех остальных аспектах жизни. Если контроллерная логика ва вью будет мешать нам разрабатывать или тестировать, мы вынесем ее в отдельное место. Software гибкое, оно все перенесет, не нужно боятся его менять.

      • Chamie
        /#10707332 / +2

        но зачем в текущей задаче и в 90% рядовых задач выносить эту логику в отдельное место
        Затем, что код логики проще искать в отдельном месте, чем выискивать его посреди разметки представления, если у вас не на одну страничку кода весь компонент. А с подходом «давайте всё инлайнить без декомпозиции» непонятно, зачем вы тогда вообще заводите модель, а потом ещё что-то от неё наследуете. Почему просто component state не использовать? Он в компоненте уже есть «забесплатно».

      • Chamie
        /#10707448 / +1

        Не смущает, потому что отправка сообщения в Smalltalk == вызов метода в современных ОО-языке.
        А в Redux отправка сообения == передача сообщения (экшна) методу dispatch. В общем, странно выглядит, когда про реально отправку сообщений прямо «по классике», к которой вы и апеллируете, вы пишете «вместо вызова методов».

  9. faiwer
    /#10706776

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


    Скажем есть у нас большое 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.

    • VolCh
      /#10706834

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

      • faiwer
        /#10706856

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

    • Telichkin
      /#10706910

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

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


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

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


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

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

    • Druu
      /#10706970 / +1

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

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

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

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

      • faiwer
        /#10707002

        Ну это вы пока со сложной логикой на редаксе не встречались

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

        • DexterHD
          /#10707042

          Вот простой пример, который мои коллеги не могли долго решить, расскажите пожалуйста как правильно его готовить:
          — Есть какая либо сложная бизнес-сущность (запись, статья, документ, не важно), у этой сущности около 100 свойств.
          — Есть страница с выводом списка этих сущностей в таблице, по 100 и больше штук на страницу (На этой странице нужен только ID, Title и еще пара полей). Подгрузка новых осуществляется с помощью запроса на сервер.
          — Есть так же страница с редактированием конкретной сущности (форма со 100 разными инпутами).
          И у нас SPA React + Redux.

          Теперь вопрос. Как в Redux правильно построить `store`? Запихнуть все в массив `records`? Но тогда на странице со списком получится 100 записей по 100 полей каждая, и куча выжратой непонятно для чего памяти. Или сделать 2 разных редьюсера `recordList` и `currentRecord`, но тогда нет единого хранилица, одни и те же данные получаются денормализованными, а редакс топит за нормализацию данных.

          • faiwer
            /#10707062 / +1

            одни и те же данные получаются денормализованными

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

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

            • DexterHD
              /#10707096 / +2

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

              • faiwer
                /#10707110 / +1

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

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

              • Chamie
                /#10707372

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

              • Atorian
                /#10707450 / +1

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

          • VolCh
            /#10707260

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

            • faiwer
              /#10707292

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

              • VolCh
                /#10707342

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

                • faiwer
                  /#10707354

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

                  • VolCh
                    /#10707462

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

                    • mayorovp
                      /#10707484

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

                      • VolCh
                        /#10707492

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

                    • faiwer
                      /#10707498 / +1

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


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

                      • VolCh
                        /#10707510

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

                    • AlexKeller
                      /#10707926

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

          • DarthVictor
            /#10707918 / +1

            >Или сделать 2 разных редьюсера `recordList` и `currentRecord`
            This.
            >но тогда нет единого хранилица, одни и те же данные получаются
            Ну запихните все записи в один объект по id и держите у каждой записи набор additionalFields и двумя селекторами `recordList` и `currentRecord` забирайте. Если для записи, показываемой на отдельном экране, нет набора additionalFields, то запрашивайте их сервера. А пока поля грузятся покажите заголовок из тех двух полей.

        • Druu
          /#10707054

          > У меня сейчас под носом довольно сложное приложение. Ради интереса посмотрел, frontend-js файлы: 37k lines, 425 files. Приложению около года. Там довольно сложный рекурсивный UI, модульная компоновка. Не встречал описанных вами проблем.

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

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

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

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

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

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

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

          • Druu
            /#10707070

            > Redux заставил меня полюбить функциональный подход

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

            • faiwer
              /#10707100 / +1

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

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

              • Druu
                /#10707164

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

                Логика работы редакса про impure. Сам стор работает при помощи грязных функций, с мутабельным изменением стейта.

                • mayorovp
                  /#10707182 / +2

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

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

                  • Druu
                    /#10707194

                    > Тоже самое можно сказать про интерпретатор Хаскеля и про его рантайм

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

                    • mayorovp
                      /#10707204 / +1

                      Вызов impure диспатча происходит из обработчика события — т.е. из безусловно императивного кода, притом находящегося за рамками области ответственности redux. Весьма странно записывать в недостатки redux, библиотеки для построения M, тот факт что она плохо подходит для построения C…

                      • Druu
                        /#10707234

                        Прошу прощения, но что находится в рамках ответственности редакса тогда, на кой черт он вообще сдался?

                        • mayorovp
                          /#10707242 / +1

                          Это библиотека для создания модели в рамках архитектуры MVC, и для связи ее с остальными буквами в этой аббревиатуре.

                          • Druu
                            /#10707264

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

                            • mayorovp
                              /#10707316

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

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

                              • Druu
                                /#10707384

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

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

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

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

                                • mayorovp
                                  /#10707410

                                  Если бы редакс был импьюрным — проблемы бы не было, и сага была бы не нужна. Просто делаем запрос из редусера — и вперед! :-)

                              • Chamie
                                /#10707414

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

                                • mayorovp
                                  /#10707434 / +1

                                  Контроллер — это то что обрабатывает пользовательский ввод. В мире JS это обработчики событий.

                                  • Chamie
                                    /#10707482

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

                                    • mayorovp
                                      /#10707488 / +1

                                      А вот как раз устройством ввода в мире HTML-песочницы и является браузер…

                                      • Chamie
                                        /#10707500

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

                                        • mayorovp
                                          /#10707522

                                          а в HTML мы наоборот вешаем обработчики на сами куски вью...

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


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

                                          • Druu
                                            /#10707556 / -1

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

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

                                            • mayorovp
                                              /#10707588

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


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

                                              • Druu
                                                /#10707622 / -1

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

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

                                                • VolCh
                                                  /#10707648 / +1

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

                                                  • Chamie
                                                    /#10707668

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

                                                    • VolCh
                                                      /#10707676 / +1

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

                                                      • Chamie
                                                        /#10707684

                                                        А если обработчик вообще ни с какими «моделями» не работает, а просто модифицирует что-то в DOM'е или лезет на сервер — его всё равно можно назвать «контроллером»?

                                                        • mayorovp
                                                          /#10707694

                                                          А чем еще его можно в таком случае назвать?

                                                          • Chamie
                                                            /#10707706

                                                            Ничем. Просто не нужно пытаться натянуть сову на глобус. HTML — это не MVC, это HTML. На нём можно (пытаться) реализовывать паттерн MVC, но сам по себе он — не MVC.

                                                  • Druu
                                                    /#10707724 / -1

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

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

                                                    • mayorovp
                                                      /#10707752

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


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


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

                                                      • Druu
                                                        /#10707796 / -1

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

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

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

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

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

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

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

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

                                                        • VolCh
                                                          /#10708194 / +1

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

                                                          • Druu
                                                            /#10708652 / -1

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

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

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

                                                            • VolCh
                                                              /#10708980

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

                                                              • Druu
                                                                /#10709294 / -1

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

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

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

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

                                                                • mayorovp
                                                                  /#10709298

                                                                  У вас очень странное понимание MVC. Не могли бы вы привести ссылки на источники?

                                                                  • TheShock
                                                                    /#10709578

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

                                                                    • mayorovp
                                                                      /#10709608

                                                                      Ну потому я и прошу источники, что Druu активно обсуждает какую-то «классическую» MVC, про которую, видимо, никто кроме него не знает.

                                                                      • TheShock
                                                                        /#10709618

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

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

                                                                        • mayorovp
                                                                          /#10709630 / +1

                                                                          Да нет, спор не терминологический. У разницы в терминах есть последствия: в зависимости от понимания MVC, метод Button1Clicked оказывается в разных местах.

                                                                          • TheShock
                                                                            /#10709678 / +1

                                                                            Да, тут не поспоришь. Но, на счет 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 — у нас нету и вопросов, куда их ложить

                                                                            • mayorovp
                                                                              /#10709888

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

                                                                              • TheShock
                                                                                /#10709920

                                                                                Да, тут вы правы, у нас есть такие компромисы

                                                                              • Druu
                                                                                /#10710352

                                                                                > потому что в предметной области нет никакой сортировки.

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

                                                                                • VolCh
                                                                                  /#10710506 / +1

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


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

                                                                                  • Druu
                                                                                    /#10710526 / -1

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

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

                                                                                    • VolCh
                                                                                      /#10710552

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

                                                                                      • Druu
                                                                                        /#10710618 / -1

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

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

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

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

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

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

                                                                                        • VolCh
                                                                                          /#10710902 / +1

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

                                                                                          • Druu
                                                                                            /#10710930

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

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

                                                                                            • VolCh
                                                                                              /#10710952 / +1

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


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

                                                                                              • Druu
                                                                                                /#10710978 / -1

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

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

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

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

                                                                                                • VolCh
                                                                                                  /#10711088

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

                                                                                                  • Druu
                                                                                                    /#10711106

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

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

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

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

                                                                                                    • VolCh
                                                                                                      /#10711114

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

                                                                                                      • Druu
                                                                                                        /#10711150

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

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

                                                                                                        • VolCh
                                                                                                          /#10711174

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

                                                                                                          • Druu
                                                                                                            /#10711196 / -1

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

                                                                          • Druu
                                                                            /#10709764

                                                                            > У разницы в терминах есть последствия: в зависимости от понимания MVC, метод Button1Clicked оказывается в разных местах.

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

                                                                    • Druu
                                                                      /#10709740 / -1

                                                                      > где Модель ни о ком не знает и только посылает события:

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

                                                                      • TheShock
                                                                        /#10709776 / +2

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

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

                                                                        • Druu
                                                                          /#10710354 / -1

                                                                          > У модели есть EventEmitter, а у того — публичный интерфейс IListener.

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

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

                                                                          • VolCh
                                                                            /#10710516

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

                                                                            • Druu
                                                                              /#10710528 / -1

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

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

                                                                              • mayorovp
                                                                                /#10710538

                                                                                Нет, это вы не можете ничего поменять. У меня таких проблем не возникает.

                                                                                • Druu
                                                                                  /#10710628

                                                                                  > Нет, это вы не можете ничего поменять.

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

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

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

                                                                              • VolCh
                                                                                /#10710566

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

                                                                                • Druu
                                                                                  /#10710638 / -1

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

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

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

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

                                                                                  • mayorovp
                                                                                    /#10710662

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

                                                                                    Бинго!

                                                                                    • Druu
                                                                                      /#10710940

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

                                                                                      • VolCh
                                                                                        /#10710958

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

                                                                                        • Druu
                                                                                          /#10710994

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

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

                                                                                          • VolCh
                                                                                            /#10711112

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

                                                                                            • Druu
                                                                                              /#10711160

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

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

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

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

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

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

                                                                                              • TheShock
                                                                                                /#10711182

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

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

                                                                                                • Druu
                                                                                                  /#10711204

                                                                                                  > Но вид не может работать, если не работает модель.

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

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

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

                                                                                                  • VolCh
                                                                                                    /#10711270 / +1

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

                                                                                                    • Druu
                                                                                                      /#10711282 / -1

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

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

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

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

                                                                                                      • VolCh
                                                                                                        /#10711318

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


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

                                                                                                        • Druu
                                                                                                          /#10711396

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

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

                                                                                                          • VolCh
                                                                                                            /#10711462

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


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

                                                                                                            • Druu
                                                                                                              /#10711468

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

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

                                                                                              • VolCh
                                                                                                /#10711190 / +1

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

                                                                                                • Druu
                                                                                                  /#10711220

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

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

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

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

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

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

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

                                                                                                  • VolCh
                                                                                                    /#10711290

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

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


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

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

                                                                                                    • Druu
                                                                                                      /#10711344 / -1

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

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

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

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

                                                                                                      • VolCh
                                                                                                        /#10711382

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

                                                                                                        • Druu
                                                                                                          /#10711422 / -1

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

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

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

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

                                                                                      • mayorovp
                                                                                        /#10710982

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

                                                                                        • Druu
                                                                                          /#10711010

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

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

                                                                                          • mayorovp
                                                                                            /#10711058 / +1

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

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

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

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

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

                                                                                            • Druu
                                                                                              /#10711130

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

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

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

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

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

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

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

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

                                                                                              • VolCh
                                                                                                /#10711194

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

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

                                                                                              • mayorovp
                                                                                                /#10711206

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

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


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


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

                                                                                                • Druu
                                                                                                  /#10711242

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

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

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

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

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

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

                                                                                                  • mayorovp
                                                                                                    /#10711248

                                                                                                    Модель может работать вовсе без вида.

                                                                                                    • Druu
                                                                                                      /#10711254

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

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

                                                                                                      • TheShock
                                                                                                        /#10711274 / +2

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

                                                                                                        • TheShock
                                                                                                          /#10711294

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

                                                                                                        • Druu
                                                                                                          /#10711304

                                                                                                          > Почему не поверит?

                                                                                                          Не п_р_оверит. Ну потому что если могут проверить — значит вид/контроллер есть :)
                                                                                                          А пока нету — проверить не смогут.

                                                                                                          • TheShock
                                                                                                            /#10711342

                                                                                                            То есть, тесты, по вашему — Вид?

                                                                                                            • Druu
                                                                                                              /#10711360 / -1

                                                                                                              MVC — это не более чем модель черного ящика. Контроллер — название для input, вид — название для output, модель — ну, сам ящик. Если вы посмотрели, что вернула ф-я, то вы посмотрели output. Это вид.

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

                                                                                                      • mayorovp
                                                                                                        /#10711288 / +1

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

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

                                                                                                        • Druu
                                                                                                          /#10711328

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

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

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

                                                                                                          • VolCh
                                                                                                            /#10711392

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

                                                                                  • VolCh
                                                                                    /#10710922

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


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


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


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

                                                                                    • Druu
                                                                                      /#10710956

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

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

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

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

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

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

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

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

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

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

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

                                                                                      • VolCh
                                                                                        /#10711056 / +1

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


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

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


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

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


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

                                                                                        • Druu
                                                                                          /#10711100 / -1

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

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

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

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

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

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

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

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

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

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

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

                                                                                          • VolCh
                                                                                            /#10711164

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

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


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

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


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

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


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

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

                                                                                            • Druu
                                                                                              /#10711186

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

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

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

                                                                                              Конечно.

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

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

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

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

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

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

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

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

                                                                                              • VolCh
                                                                                                /#10711246

                                                                                                Конечно

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


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

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


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

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


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

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

                                                                                                • Druu
                                                                                                  /#10711264

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

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

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

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

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

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

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

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

                                                                                                  • VolCh
                                                                                                    /#10711356

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

                                                                                                    • Druu
                                                                                                      /#10711376

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

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

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

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

                                                                                                      • VolCh
                                                                                                        /#10711442

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


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

                                                                                                        • Druu
                                                                                                          /#10711458

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

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

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

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

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

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

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

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

                                                                          • oxidmod
                                                                            /#10710550

                                                                            Отсылка сообщения эквивалентна вызову метода


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

                                                                • TheShock
                                                                  /#10709570

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

                                                                  Контроллер знает о Модели и Вид знает о Модели. Разве этого недостаточно?

                                        • Druu
                                          /#10707524

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

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

                                          • Chamie
                                            /#10707574

                                            Так я, собственно, про это и писал — что HTML на MVC «в классическом понимании» не ложится.

                                    • VolCh
                                      /#10707504

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

                                • Telichkin
                                  /#10707544

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

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

                                  • Chamie
                                    /#10707586

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

                                    • Telichkin
                                      /#10707602

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

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

                                      • Chamie
                                        /#10707620

                                        Так вы же к вот этому комментарию вопрос задавали:

                                        нет смысла его выделять и проецировать использование Redux и React на эту схему

                • faiwer
                  /#10707192

                  Логика работы редакса про impure. Сам стор работает при помощи грязных функций, с мутабельным изменением стейта

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

                  • Druu
                    /#10707210

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

                    • faiwer
                      /#10707228

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

              • TheShock
                /#10707430

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

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

                • faiwer
                  /#10707456 / +1

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

                  • TheShock
                    /#10707468

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

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

                    • Chamie
                      /#10707494

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

                      • TheShock
                        /#10707520

                        А разве делейность как-то отменяет его процедурность? Ну ладно, пусть диспатч будет таким: «запиши ка в один глобальный массив измененения которые ты должен будешь внести потом в другом глобальном массиве».

                        • Chamie
                          /#10707612

                          Смотрите, если диспатч только добавляет в очередь, то

                          function(dispatch){
                            dispatch("aaa");
                            dispatch("bbb");
                            dispatch("ccc");
                          }
                          будет аналогом вызова чистой функции:
                          dispatch(function(){
                            let actions = [];
                            actions.push("aaa");
                            actions.push("bbb");
                            actions.push("ccc");
                            return actions;
                          })

                          • TheShock
                            /#10707624

                            Не, диспатч аналог грязной функции:

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


                            Вы вообще понимаете отличия pure от impure? У вас ведь нету там return, сами видите, или нет?

                            • Chamie
                              /#10707640

                              Return нет, но dispatch — это входящий параметр, а не глобальная функция.

                              • TheShock
                                /#10707666 / +1

                                Чуть в том, что dispatch «меняет что-то там», вместо «возвращает новые данные». Это называется «side-effect» и он делает функцию impure

                                • Chamie
                                  /#10707678

                                  Я, вообще-то, так и писал, что «при таких-то условиях эта функция — аналог вызова чистой функции», а не что функция с вызовами dispatch — чистая сама со себе.

                                  • TheShock
                                    /#10707696 / +1

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

                                    • Chamie
                                      /#10707710

                                      Вы приписываете мне то, чего я не писал. Давайте я для вас выделю подлежащее и сказуемое: «Функция — аналог вызова». Не «вызов — аналог вызова», не «функция — аналог функции».
                                      Давайте так: тело этой «грязной» функции можно заменить на вызов «чистой» функции с передачей результата вызова этой чистой функции в dispatch(). Так понятнее, что я имел в виду?

                                      • TheShock
                                        /#10707720

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

                                        Если не верите, то попробуйте. Напишите работающий код pure диспатча, а не псевдокод. Без сайд-эффектов. И увидите, что ничего не заработает.

                                        • Chamie
                                          /#10707730

                                          Я писал где-то, что dispatch у нас чистый? Откуда вы это находите у меня в сообщениях?

                                          • TheShock
                                            /#10707754

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

                                            Как могут грязные носки быть аналогом одевания чистых носков?

                                            • Chamie
                                              /#10707768

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

                                              • TheShock
                                                /#10707824

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

                                                • Chamie
                                                  /#10707850

                                                  Да пожалуйста:
                                                  Оригинал — грязная функция, вызывающая 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()?

                                                  • TheShock
                                                    /#10707858

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

                                                    • Chamie
                                                      /#10707866

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

                                                      • TheShock
                                                        /#10707898

                                                        Функция 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() означает «пойди ка поменяй какие-то поля в стейте», обычная императивщина.

                                                        • Chamie
                                                          /#10707952

                                                          Функция 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));
                                                          }

                                                          • faiwer
                                                            /#10707966 / +1

                                                            • Chamie
                                                              /#10707972

                                                              Повторю и вам, если надо:

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

                                                              • TheShock
                                                                /#10707976

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

                                                                • Chamie
                                                                  /#10707992 / -1

                                                                  Та насрать на функцию pure, вы ее ввели просто чтобы спрятать свою оплошность
                                                                  А, я понял. Вам насрать на то, что я написал, вы спорите с тем, что вы сами придумали.

                                                                  • TheShock
                                                                    /#10707998 / +1

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


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

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


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

                                                                    • Chamie
                                                                      /#10708022

                                                                      Подмена тезиса — у вас. Я «влез» только спросив про детали вызова dispatch, а потом уже вы мне про моё утверждение стали доказывать, что оно неверное.

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

                                                                      • TheShock
                                                                        /#10708048

                                                                        Значит, я вас неправильно понял и вы не пытались отрицать грязную процедурную природу dispatch, а просто задали вопрос по другой теме?

                                                                        • Chamie
                                                                          /#10708050

                                                                          Да.

                                                                          • TheShock
                                                                            /#10708060

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

                                                                            2 + 3
                                                                            // можно заменить на:
                                                                            
                                                                            sum = (a, b) => a + b
                                                                            sum(2, 3)
                                                                            
                                                                            // можно заменить на:
                                                                            
                                                                            sum = a => b => a + b
                                                                            getTwo = () => 2
                                                                            getThree = () => 3
                                                                            sum(getTwo())(getThree())

                                                                            Я ответил на ваш вопрос?

                                                                            • Chamie
                                                                              /#10708120

                                                                              Мой аргумент был про то, что при работе с Redux'ом мой собственный код может быть вполне чистым функциональным, если dispatch будет вызываться инфраструктурой автоматически.

                                                                              • TheShock
                                                                                /#10708126 / +1

                                                                                Пример, пожалуйста.

                                                                                • Chamie
                                                                                  /#10708136

                                                                                  Ну, например, переписать mapDispatchToProps (или написать аналог), который при создание bound action creator'ов будет заворачивать их в аналог моей dirtyCallsPure. Тогда весь бизнес-код с action creator'ами будет чистыми функциями.

                                                                                  • TheShock
                                                                                    /#10708144 / +1

                                                                                    Как переписать? Как потом это юзать? Покажите пример, а не гипотетические мечты рассказывайте) В том то и дело, что если бы можно было бы — так и сделали бы.

                                                                                    • Chamie
                                                                                      /#10708176

                                                                                      Ок, вот вам из доков стандартный вид 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.

                                                                                      • TheShock
                                                                                        /#10708206

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


                                                                                        this.props.onTodoClick()

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

                                                                                        • Chamie
                                                                                          /#10708394

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

                                                                                          • TheShock
                                                                                            /#10708490

                                                                                            this.props.onTodoClick() 

                                                                                            Это ваш код, не фреймворковский. И он грязный. он изменяет стейт как обычная процедура. Где ваш код чистый? Вы вообще понимаете, что такое pure?

                                                                                            • Chamie
                                                                                              /#10709812

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

                                                                                              • faiwer
                                                                                                /#10709814 / +1

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

                                                                                                • Chamie
                                                                                                  /#10709830

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

                                                                                              • TheShock
                                                                                                /#10709820

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

                                                                                                • Chamie
                                                                                                  /#10709838

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

                                                                                                  • TheShock
                                                                                                    /#10709874

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

                                                                                                    • Chamie
                                                                                                      /#10709890

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

                                                                                                      • TheShock
                                                                                                        /#10709916

                                                                                                        А LINQ делает из C# функциональный язык?

                                                                                                        • Chamie
                                                                                                          /#10709952

                                                                                                          Поддержка чистых функций делает C# (как и JS) мультипарадигменным языком

                                                                                                          • TheShock
                                                                                                            /#10709960

                                                                                                            Язык С поддерживает чистые функции, но при этом он процедурный язык)

                                                                                                            Редакс использует некоторые идеи из FP, да. Как и много чего другого. Черт, та я сейчас игру на Юнити пишу, там столько просто тьма ФП, но я ведь не говорю, что пишу в функциональном стиле. Я пишу в ООП стиле с элементами ФП)

                                                                                                            • Chamie
                                                                                                              /#10709970

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

                                                                                                              • TheShock
                                                                                                                /#10709984 / +1

                                                                                                                Я же писал снизу пример истинно функционального кода на JS. Там 100% ФП. Очевидно, все функциональным быть не может, фреймворк будет процедурным, но клиентский код на таком фреймворке будет чисто-функциональным.

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

                                                                                                                Конечно продолжу, ведь если у меня 60% ФП и 40% ПП, то у меня 2 из 5 функций — грязные. Какое ж это ФП? Как я уже приводил пример — когда в С используешь чистую функцию рассчета корня — ты не используешь ФП, ты просто используешь чистую функцию. ООП и ПП их не отрицают, чистые функции.

                                                                                                                • Chamie
                                                                                                                  /#10710056

                                                                                                                  Так, знаете, IO, например, в принципе не может быть чистым, значит, и Haskell весь «грязный»? (getLine, вызванная из кода с одними и теми же параметрами вернёт каждый раз разные значения).

                                                                                                                  • Druu
                                                                                                                    /#10710360

                                                                                                                    > Так, знаете, IO, например, в принципе не может быть чистым, значит, и Haskell весь «грязный»?

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

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

                                                                                                                    redux-saga — чистые (точнее — на них _можно_ писать чисто, можно, конечно, и не).

                                                                                                                • Chamie
                                                                                                                  /#10710084

                                                                                                                  Я же писал снизу пример истинно функционального кода на 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);

                                                                                                                  • TheShock
                                                                                                                    /#10710096 / +2

                                                                                                                    Мне нравится, как вы ушли в крайность)

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

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

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

                                                                                                                    • faiwer
                                                                                                                      /#10710106

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

                                                                                                                      • TheShock
                                                                                                                        /#10710114

                                                                                                                        json файл как инишиалСтейт и функция main, которую автоматически запускает приложение.

                                                                                                                    • Chamie
                                                                                                                      /#10710118

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

                                                                                                                      Но заметьте что есть разница — процедурно инициировать приложение (а дальше оно работает функционально), или процедурно строить всю архитектуру, где кое-какие места будут работать функционально.
                                                                                                                      В моём варианте весь «грязный» код был компоненте и во фреймворке, при этом и редьюсеры, и экшн-креэйторы, и экшн-генераторы были чистыми. У вас редьюсер (increaseCounter) запихнут в компонент, экшнов вообще нет, и всё равно фреймворк и компонент — «грязные».
                                                                                                                      // Так фреймворк обрабатывает клики:
                                                                                                                      var newState = markup.div.onClick(oldState);
                                                                                                                      Под «фреймворк обрабатывает клики» вы предполагаете, что это часть которого фреймворка, Реакта? (а какой ещё фреймворк тут знает всю структуру компонента со всеми хэндлерами?)
                                                                                                                      Тогда вы просто пошли дальше по моему пути — воспользовались тем, что Реакт всё равно «грязный» и засунули туда («под ковёр») всю «грязную» часть интеграции с Редуксом (вызов экшн-генератора из хэндлера).

                                                                                                                      • TheShock
                                                                                                                        /#10710294

                                                                                                                        Еще раз, при редаксе изменения стейта — грязные. Увы, но это так.
                                                                                                                        В моем примере (это пример, не готовая либа) изменения стейта, да и весь клиентский код — чистый. И компонент чистый. Посмотрите внимательно — компонент — это чистая функция, которая возвращает объект, он никогда не изменяет стейт.
                                                                                                                        Увы, но это так. Я вижу, что вы расстроены, мне жаль, но редакс — предоставляет процедурный API с функциональными элементами. Каждый раз, когда необходимо изменить стейт — вы берете и изменяете его вызовом процедуры dispatch, так или иначе. Вон faiwer понимает это и не парится, пользует процедурный редакс дальше.
                                                                                                                        Абрамов себе понимает, что настоящее ФП его целевой аудитории не зайдет — потому держит баланс — и вот как клево, что большинство выбирает именно редакс при наличии лучшей альтернативы в виде MobX.

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

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

                                                                                                                        Так вот — может хватить верить в божественную непогрешимость этой библиотеки и, наконец-то, понять его недостатки?

                                                                                                                        • Chamie
                                                                                                                          /#10710308

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

                                                                                                                          • TheShock
                                                                                                                            /#10711154

                                                                                                                            Вы имеете в виду вызовы dispatch и вызовы методов из компонента? Да, это так. А с этим кто-то спорил?

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

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

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

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

                                                          • TheShock
                                                            /#10707974

                                                            Намного более простой пример чистой функции — функция `add = (a, b) => a + b`. И она ровно так же не имеет никакого отношения к спору. Повторю вопрос. И прошу, перестаньте уходить от темы и доказывать что-то, о чем мы вообще не общались.

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


                                                            Еще раз — прекратите писать неважные вещи чтобы уйти в более простую тему и вернитесь к сути спора.

                                                            • Chamie
                                                              /#10708014

                                                              Вернулся (впрочем, и не уходил).

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

                                                              • TheShock
                                                                /#10708024

                                                                тело этой «грязной» функции можно заменить на вызов «чистой» функции с передачей результата вызова этой чистой функции в dispatch()

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

                                                                Потому что пока вы взяли свою же грязную функцию, заменили строковые аргументы на вызов функции, которая возвращает эти строковые компоненты и начали называть ее чистой
                                                                function dirty(dispatch){
                                                                  dispatch("a");
                                                                  dispatch("b");
                                                                  dispatch("c");
                                                                }

                                                                • Chamie
                                                                  /#10708046

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

                                                                  • TheShock
                                                                    /#10708054

                                                                    Вы ведь хотели заменить тело грязной функции на вызов чистой функции? Цитирую:

                                                                    тело этой «грязной» функции можно заменить на вызов «чистой» функции

                                                                    Вы не заменили тело, а добавили просто вызовы чистой функции. Объясните, что вы стараетесь сделать? Вы точно не запутались?

                                                                    • Chamie
                                                                      /#10708122 / -1

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

                                                                      • Chamie
                                                                        /#10708442 / -1

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

                                                                        • TheShock
                                                                          /#10708492

                                                                          По-моему у вас просто какая-то каша в голове.

                                                                          • Chamie
                                                                            /#10709816

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

                                                                            • faiwer
                                                                              /#10709822 / +1

                                                                              и тогда грязный код будет только во фреймворке

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

                                                                              • Chamie
                                                                                /#10709840

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

                                                                                • faiwer
                                                                                  /#10709846 / +1

                                                                                  Но, скажем, интерпретатор Хаскеля — не чистый. Разве это делает весь код на Хаскеле грязным?

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


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

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


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

                                                                                  • Chamie
                                                                                    /#10710124

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

                                                                            • TheShock
                                                                              /#10709824

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

                                                                              • faiwer
                                                                                /#10709834

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

                                                                                • TheShock
                                                                                  /#10709872 / +1

                                                                                  Там тоже будет 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 будет работать исключительно со стейтом.

                                                                                  • faiwer
                                                                                    /#10709886

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


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

                                                                                    • TheShock
                                                                                      /#10709928 / +1

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

                                                                              • Chamie
                                                                                /#10709842

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

                                                      • Druu
                                                        /#10708104

                                                        Эдак можно любую грязную ф-ю f помощи грязной ф-и g заменить чистой ф-ей id: g(f) = id(f)();

                                                        • Chamie
                                                          /#10708132

                                                          Расскажете, как? А то у меня в итоге функция грязная выходит, я её даже так и назвал dirtyCallsPure.

                          • Druu
                            /#10707638

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

                            • Chamie
                              /#10707652

                              Не понял, распишите подробнее, пожалуйста. В общем случае очередь не обязана быть общей, это же просто массив экшнов (объектов с данными), по результату выполнения action creator'а Redux получает просто массив с очередной пачкой экшнов, с которыми уже поступает на своё усмотрение — может добавить куда-то (в какую-нибудь другую очередь, например, возможно и глобальную), может разбить.

                              • Druu
                                /#10707744

                                Ну как вы из нескольких разных точек получите один массив, который надо выполнить внутри редакса? Вы имеете ввиду потом руками как-то объединить? А потом что? Надо же дернуть «посчитайка новый стейт на данном массиве» и потмо обновить стейт.

                                • Chamie
                                  /#10707782

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

                      • mayorovp
                        /#10707548

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

                    • faiwer
                      /#10707518

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


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

                      • TheShock
                        /#10707534 / -1

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

          • faiwer
            /#10707092

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

            <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 из поста. Я не могу даже бегло оценить над чем оперирует каждая модель пока пристально её не изучу. Чем не чёрный ящик? Сегодня автор её написал в одном стиле. Завтра что-нибудь заоптимизировал — стало всё по-другому. Получаются такие слои проекта. И если проект достаточно долгий то за счёт этакой анархии мы приходим к тому, что у нас много проектов внутри одного и мы боимся хоть к чему-нибудь прикоснуться.

            • DexterHD
              /#10707140

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

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

              • faiwer
                /#10707170

                Вот и представьте

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


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

                • DexterHD
                  /#10707196

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

                  С точки зрения интерфейса? Я бы сказал «среднего размера» приложение.

            • Druu
              /#10707186 / +1

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

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

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

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

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

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

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

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

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

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

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

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

              • faiwer
                /#10707216 / +1

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

                Кажется я понял вас. Просто если скажем на форме расположено 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();
                    }

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

                • Druu
                  /#10707248

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

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

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

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

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

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

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

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

                  • faiwer
                    /#10707274

                    а каждый такой запрос — уже три экшена

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


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

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


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

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


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

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


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

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

                    • Druu
                      /#10707308

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

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

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

                      С просто методами — достаточно неплохо помогает, особенно если это ts.

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

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

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

                      То есть, вы предлагаете вынести эту часть состояния из редакса, так?

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

                      В каком смысле нет? Данные — это поля, код — это методы. Они by design разделены, по своему определению. То, что у вас поля и методы расположены в одном классе вместо того, чтобы быть расположены в одном неймспейсе (как это сделано в редаксе) ничего абсолютно не меняет. Важно, что методы имеют полный доступ к данным.

                      • faiwer
                        /#10707346 / +1

                        Потому что это разные запросы (зачастую — именно разные типы, с совершенно разным характером возвращаемых данных)

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

                        • Druu
                          /#10707454

                          > Нет, скорее так: 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. Разница огромная.

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

                          • faiwer
                            /#10707490

                            Ну и у вас по куче экшенов на форму. Не понял, что вы тут выиграли?

                            Один action. Я там указал, что он принимает на входе имя поля (точнее там должен быть path)


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

                            Какое это имеет отношение к action-ам? Зачем это знать action-у? Причём тут кеширование? Причём тут внутренности работы запроса? Давайте ещё на backend перейдём и начнём нюансы redis обсуждать.


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

                            Эта разница лежит не в action-е. Эта разница лежит в редьюсере. Action как раз универсален. Вы ещё начните писать setAge12, setAge13, setAge14. Не надо экшнами код писать, это дичь. Экшны это высказывание намерений о том, какого рода событие произошло, чтобы reducer на это отреагировал.


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

                            Action-ы к этому НЕ имеют НИКАКОГО отношения. Вы ооочень странно готовите redux.


                            лежит состояние чекера.

                            И этот чекер управляется всё тем же самым единственным setField('myCheckerName', true).


                            Можно вопрос? Вы по роду деятельности пишете на Java 6? :)

                            • Druu
                              /#10707584

                              > Эта разница лежит не в action-е. Эта разница лежит в редьюсере. Action как раз универсален.

                              Обычно редьюсер определяет тип экшона (и логику совю) по полю type. Вы предлагаете просто вместо поля type использовать другое поле. У вас не стал тут «один экшон», вы просто переместили квалификатор типа из одного поля в другое. Логика для каждого типа все равно в редьюсере будет своя. И все равно ее трудно будет отслеживать.

                              > Какое это имеет отношение к action-ам? Зачем это знать action-у? Причём тут кеширование? Причём тут внутренности работы запроса?

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

                              > Action-ы к этому НЕ имеют НИКАКОГО отношения. Вы ооочень странно готовите redux.

                              Экшоны имеют отношение к действиям пользователя и реакции внешней среды (ответ от сервера, например). Ввод данных в поле Х и поле Y — два действия пользователя. Разных. Если на два разных действия пользователя надо реагировать (в редьюсере) существенно по-разному, то эти два действия семантически соответствуют разным экшонам (вы где угодно и как угодно можете кодировать тип экшона, главное, что в редьюсере будет полноценная ветка обработки этого типа). Если результат валидации порождает разные существенные последствия (а не только те, что можно обобщить в виде «вывели под элементом Х сообщение об ошибке Y», например), то вам нужна эта ветка в редьюсере, а значит у вас _по факту_ есть +1 тип экшонов. Еще раз — вы можете как угодно и где угодно кодировать этот тип, но это экшон нового типа де-факто, т.к. на него есть уникальная реакция редьюсера.

                              • faiwer
                                /#10707626 / +1

                                У вас не стал тут «один экшон», вы просто переместили квалификатор типа из одного поля в другое

                                Незачем эти вещи делать в action-ах. И незачем городить для этого такой огромный зоопарк.


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

                                Мне не нравится такая формулировка. Дело в том, что мозг приложения это как раз редсьюсеры. И ветвления кода там это более чем-нормально. Особенно если речь идёт о примерно одинаковых вещах, с небольшими различиями. Более того, никто не заставляет вас в reducer-ах писать switch-ы. Вы можете обсустраивать там логику как вам душе будет угодно. И очевидно, что группировать однотипные, но всё же отличные в мелочах вещи, там будет проще. И писать такое проще. И читать такое проще. И копипасты примерно 0. Откуда вам вообще пришла в голову мысль для каждого setter-а городить отдельный action? Из java? :)


                                Логика для каждого типа все равно в редьюсере будет своя.

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


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

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


                                Ввод данных в поле Х и поле Y — два действия пользователя. Разных. … и многое другое

                                Нет, это не так. Вы сами придумали эту проблему и теперь героически с ней сражаетесь. Следующий шаг ? setAge13.

                                • Druu
                                  /#10707680 / -1

                                  > Незачем эти вещи делать в action-ах.

                                  А где, прошу прощения? Единственный способ обновить данные в сторе — дернуть редьюсер через диспатч экшона. Единственный способ избавиться от экшенов — убрать данные из стора.

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

                                  В редьюсерах только самая тривиальная логика. Мозг — асинхронные экшоны.

                                  > Более того, никто не заставляет вас в reducer-ах писать switch-ы.

                                  Да какая разница свитчи, ифы или еще как? Смысл в том что в редьюсере должен быть кейз с обработкой.

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

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

                                  > Откуда вам вообще пришла в голову мысль для каждого setter-а городить отдельный action?

                                  Оттуда, что никакого другого способа обновить стор — в редаксе нет. Только через отдельный экшн.

                                  > Только кастомная логика будет своя.

                                  Ну она и есть обычно кастомная.

                                  > Если архитектуру писал не орангутанг, то это более чем формализуемо.

                                  Формализуемо что?

                                  > К примеру валидацию можно дёргать по тому же имени поля, передавая туда все нужные параметры.

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

                                  > Это будет краткий и универсальный код.

                                  Он будет универсальный, только если вам нужно делать _одно и то же_.

                                  > Нет, это не так.

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

                                  • Chamie
                                    /#10707702 / +1

                                    код в … асинхронных экшенах
                                    Как вы код в пакеты с данными запихиваете? Экшн — это же просто данные.

                                    • Druu
                                      /#10707770

                                      Асинхронный экшн — не только данные, конечно же. На то он и асинхронный.

                                      • Chamie
                                        /#10707784

                                        Так он и не экшн тогда.

                                        • Druu
                                          /#10707818

                                          Как не экшн? Все, что можно засунуть в dispatch (и с чем он корректно отработает, конечно) — экшн.

                                      • faiwer
                                        /#10707800

                                        Druu, относитесь к экшнам как plain-object, даже если они вынужденно-асинхронные. Избегайте туда писать любую логику. Прямо по пальцам линейкой… Чем тупее async-action, тем лучше. Вплоть до замены его на какой-нибудь декларативный middleware.


                                        Задача action-а в экомистеме redux-а только одна: "выразить намерение". Сказать reducer-у, что неплохо было бы сделать что-то. Чем декларативнее, тем лучше. Поэтому почти всегда (в некоторых приложениях 100%) они представляют из себя plain object.


                                        Это не место для логики. И уж тем более для бизнес-логики. Мы вынуждено пишем там наши await api.someApi(). Это такой грязный костыль. И он там только по той причине, что никто (вроде) ещё не придумал как сделать лучше. В reducer-е этого нет, т.к. это святая святых. Он просто обязан быть pure (reducer), поэтому туда никак. А больше, по сути и некуда.


                                        Файлы с action-ми должны быть предельно худыми. Вся логика в редьюсерах (и вспомогательных к ним файлах). Это redux way.

                                        • Chamie
                                          /#10707812

                                          А что не так с асинхронными экшн-креэйторами?

                                          • faiwer
                                            /#10707826 / +1

                                            А что с ними так? :) Их даже нет в стандартной поставке redux-а. Имеют массу реализаций. Я использовал только thunk. Он по сути пробрасывает туда dispatch и getState, даёт полный карт-бланш. Практически абсолютная свобода. Вместо простого флажка "я хочу" мы получаем некий god-mode. И всё это ещё и асинхронно. Прервав его выполнение посередине (например какой-нибудь reload таба) мы получаем ерунду в store.


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

                                            • Chamie
                                              /#10707874

                                              А что с ними так? :) Их даже нет в стандартной поставке redux-а
                                              А что там должно быть «в поставке»? Написать async в сигнатуре функции — это нужно что-то поставлять?

                                              • Druu
                                                /#10708082

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

                                                • Chamie
                                                  /#10708124

                                                  В сигнатуре функции, создающей bound action creator.

                                                • faiwer
                                                  /#10708190 / +1

                                                  Судя по всему, Chamie имел ввиду использовать следующий трюк:


                                                  • используем react-redux
                                                  • используем mapDispatchToProps в виде нотации dispatch => object
                                                  • в object в качестве actionCreator-в используем async actionCreator-ы которые за-bind-ны на dispatch

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

                                                  • Chamie
                                                    /#10708400 / -1

                                                    Вы так пишете, будто это что-то сложное и нестандартное, а не просто добавить 1 слово (async) в стандартное приложение на react-redux.

                                                    • faiwer
                                                      /#10708692

                                                      Ну потому что это не добавить 1 слово async в стандартное приложение. Вот почему. Это организовать все action-ы строго через mapDispatchToStore, да ещё и пере-bind-ив их на конкретный экземпляр dispatch. Ну либо прямо написав их внутри этой mapDispatchToStore фабрики и таким образом, воспользовавшись замыканием. А то, что при этом это работает в обход redux-а, не принципиальный момент? То что вызвав store.dispatch(asyncAction()) у вас приложение упадёт с ошибкой (т.к. это вообще не action) — это разве мелочь?


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

                                                      • Chamie
                                                        /#10709844

                                                        А то, что при этом это работает в обход redux-а, не принципиальный момент? То что вызвав store.dispatch(asyncAction()) у вас приложение упадёт с ошибкой (т.к. это вообще не action) — это разве мелочь?
                                                        Вы путаете. Эта функция — не action creator, поэтому её возвращаемое значение и нельзя задиспатчить — она не возвращает action.
                                                        Но асинхронным action-ом это, конечно, не станет.
                                                        Я, вообще-то, именно про это и писал — мол, зачем создавать какие-то асинхронные action'ы, если можно просто вызывать dispatch обычных action'ов в асинхронном коде?

                                                        • faiwer
                                                          /#10709852 / +1

                                                          Эта функция — не action creator, поэтому её возвращаемое значение и нельзя задиспатчить — она не возвращает action

                                                          Я ничего не путаю. Всё верно. Эта функция не action-creator, как можно подумать изначально. Мы ведь типа в mapDispatchToStore должны список actionCreator-ов передать. Но… нет. Можно и всякую хрень туда слать. Оказывается оно пережуёт.


                                                          если можно просто вызывать dispatch в асинхронном коде?

                                                          Грубо очень. Вообще не по-феншую :) Это же по сути кусочки бизнес-логики… Которые по идее должны лежать 2 стадиями дальше. Но их туда не положить. Получается чепуха какая-то. И в итоге мы явным образом во View вызываем руками самостоятельно методы с бизнес-логикой, вообще в обход framework-а.


                                                          У этого подхода я вижу только 1 плюс: ну оно и правда работает.

                                                          • Chamie
                                                            /#10709884

                                                            Мы ведь типа в mapDispatchToStore должны список actionCreator-ов передать.
                                                            Имеете в виду mapDispatchToProps? Нет, не так. В доках пишут, что мы должны передать туда не список actionCreator'ов, а функцию, которая принимает dispatch и возвращает список функций, которые будут вызывать actionCreator'ы и этот переданный ей dispatch.
                                                            Скриншот из доков Redux

                                                            • faiwer
                                                              /#10709898

                                                              Лезу в репу и читаю:


                                                              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".


                                                              О чём и речь. У меня цензурных слов на это слов нет :)

                                                              • mayorovp
                                                                /#10709938

                                                                Да нет, тут все понятно. Если параметр mapDispatchToProps — объект (не функция которая возвращает объект, а просто объект!) — то он должен состоять из голых actionCreator-ов. А если он функция — то она должна вернуть объект, состоящий из частичных применений композиций dispatch и actionCreator-ов.

                                                                • faiwer
                                                                  /#10709942

                                                                  состоящий из частичных применений композиций dispatch и actionCreator-ов

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

                                                              • Chamie
                                                                /#10709940

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

                                                                It’s up to you to return an object that somehow uses dispatch to bind action creators in your own way
                                                                Т.е., «это уже ваше дело будет вернуть объект, который сможет как угодно использовать dispatch, и прибайндить туда action creator'ы так, как вам удобнее».

                                                                • faiwer
                                                                  /#10709946

                                                                  На самом деле я могу и так сделать:


                                                                  setTimeout(() => window.store.dispatch({ type: 'KILL_ALL_HUMANS' }), random());

                                                                  Это примерно в той же степени моё дело ;)

                                        • Druu
                                          /#10707836

                                          > Druu, относитесь к экшнам как plain-object, даже если они вынужденно-асинхронные. Избегайте туда писать любую логику. Прямо по пальцам линейкой… Чем тупее async-action

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

                                          > Вся логика в редьюсерах (и вспомогательных к ним файлах).

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

                                          > Мы вынуждено пишем там наши await api.someApi()

                                          Так об том и речь. Когда у вас много логики на асинхронщине — редакс превращается в говно, а на практике в СПА _много_ асинхронной работы со стейтом (ну вот вам везет, у вас мало).

                                          • faiwer
                                            /#10707856

                                            редакс превращается в говно

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

                                            • Druu
                                              /#10708088

                                              > Всё что угодно превратится в оное, если его так причудливо выворачивать

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

                                              > Ваш подход работы с redux-ом напоминает мне питание через клизму.

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

                                              • faiwer
                                                /#10708114

                                                Есть. Писать худые 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 экшнов :) Он будет маленький и без копипасты.

                                                • Druu
                                                  /#10708654

                                                  > Есть. Писать худые action-ы, оставляя в них минимально необходимое (вызовы async-api).

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

                                                  > И не будет 1000 if-ов в reducer-е. И не будет 1000 action-ов.

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

                                                  • faiwer
                                                    /#10708706

                                                    Это просто математический факт, ну о чем тут можно спорить, ей-богу?

                                                    Вот я и думаю, о чём? Если вы привыкли писать вот так, или вот так, ну или вот так… То это может быть не очевидным, что обычно люди группируют однообразную функциональность в одном месте. И тогда, внезапно, получается, что даже при некотором различии в обработке конкретных полей данных, их обрабатывает 1 участок весьма компактного универсального кода, а не 1000. А ввиду того, что JS это язык со слабой динамической типизацией, это всё реализуется за 1 присест.

                                                    • Druu
                                                      /#10709302 / -1

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

                                                      Да при чем тут однообразная функциональность? В каждом случае логика обработки будет разной.

                                                      > их обрабатывает 1 участок весьма компактного универсального кода, а не 1000

                                                      Этот один участок универсального кода должен выглядеть тогда как eval(x).

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

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

                                                      • TheShock
                                                        /#10709600

                                                        Да при чем тут однообразная функциональность? В каждом случае логика обработки будет разной.

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

                                                        • faiwer
                                                          /#10709772

                                                          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 это и правда оправдано.

                                                          • TheShock
                                                            /#10709790 / +1

                                                            На сколько я знаю, в Фейсбуке Редаксом не пользуются, а Абрамов — просто рядовой разработчик. Эту идеологию он продвигает не в своей компании, а в среде фанатов.

                                                      • faiwer
                                                        /#10709766

                                                        В каждом случае логика обработки будет разной.

                                                        Мне кажется, что у вас просто вселенский бардак в кодовой базе. Оттого и кажется, что логика столь вариативна, что DRY идёт лесом. Либо передоз какой-то технологии, в которой на каждый чих создаётся новая сущность (java?).

                                                        • Druu
                                                          /#10710364

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

                                                          Ну если согласно требованиям в ответ на одно действие надо сделать Х, а в ответ на другое Y — то логика разная, чего же тут поделаешь? Да, как я уже выше говорил — все можно обобщить, в пределе до eval. Штука в том, что редакс тогда нафиг не нужен, он просто дает бесполезную лишнюю индирекцию. Смысл в редаксе есть лишь до тех пор, пока обобщение отсутствует.

                                  • faiwer
                                    /#10707704

                                    в редьюсерах получается одна огромная портянка на кучу ифов

                                    Зачем там куча if-ов? Серьёзно. Есть же целая куча DRY паттернов работы с кодом на JS.


                                    Ну она и есть обычно кастомная

                                    95% кода сложных форм это обрыдлая копипаста. Отличаются справочники, пути к серверу, конфигурация валидации и прочая мелочь. Действительно кастомной логики не более 5%. Уж тем более в этом кровавом ынтерпрайзе.


                                    В редьюсерах только самая тривиальная логика. Мозг — асинхронные экшоны.

                                    оО. WAT? Вы знаете… Я пасс. После такого я не готов дальше дискутировать. Вы меня нокаутировали.

                                  • mayorovp
                                    /#10707738 / +1

                                    В редьюсерах только самая тривиальная логика. Мозг — асинхронные экшоны.

                                    И этот человек запрещал мне писать логику в мидлварях!

                              • Chamie
                                /#10707630 / +1

                                Обычно редьюсер определяет тип экшона (и логику совю) по полю type. Вы предлагаете просто вместо поля type использовать другое поле. У вас не стал тут «один экшон», вы просто переместили квалификатор типа из одного поля в другое. Логика для каждого типа все равно в редьюсере будет своя. И все равно ее трудно будет отслеживать.
                                Так в экшне редьюсеру должно придти только новое значение для поля, делать он будет одно и то же. Выглядеть редьюсер будет примерно так:
                                function(state, action){
                                  return {
                                    ...state,
                                    madFormFields: {
                                      ...state.madFormFields,
                                      [action.fieldName]: action.fieldValue
                                    }
                                  }
                                }

                                • Druu
                                  /#10707698

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

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

                                  • Chamie
                                    /#10707722

                                    Делает одно и то же — обновляет поле. А что вы ещё хотите туда запихать?

                    • VolCh
                      /#10707350 / +2

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

                • DexterHD
                  /#10707252

                  Кажется я понял вас. Просто если скажем на форме расположено 25 <input/>-ов, то у меня это будет 1 action

                  Конечно когда у вас 25 однообразных инпутов все просто и можно запилить 1 action. А теперь представьте что за изменением каждого из инпутов стоит совершенно разная бизнес логика. Что делать?

                  • faiwer
                    /#10707284 / +1

                    Что делать?

                    Никогда не встречал форм, где было бы 25 input-ов, и все они имели своё собственное поведение. А уж тем более в тех проектах, которые вы описали выше. Они там в 99% случаев одинаковые до тошноты. Как и сами проекты, ИМХО. Можно скриншот такой формы? :) Я боюсь, что типичный пользователь ПК, встретив такой цирк, с визком и ором закроет вкладку и уйдёт к конкуренту. Если юзер подневольный, ждите падения производительности. Он просто запутается.

                    • Druu
                      /#10707360

                      > Я боюсь, что типичный пользователь ПК, встретив такой цирк, с визком и ором закроет вкладку и уйдёт к конкуренту.

                      Мы же говорили о сложных интерфейсах (к каковым, конечно, ни лента твиттов ни счетчик числа сообщений не относятся, по понятным причинам).

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

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

                      > Если юзер подневольный, ждите падения производительности.

                      Падение производительности по сравнению с чем? С отсутствием решения задачи? :)

                      • faiwer
                        /#10707380

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


                        Падение производительности по сравнению с чем? С отсутствием решения задачи? :)

                        По сравнению с тем, когда контролы выглядят одинаково, работают идентично, ввиду того, что построены на одной кодовой базе. Это касается и валидации, и справочников, и всяких хинтов, и различного интерактива вроде стадий, скрытия/показа блоков, авто-вычисляемых полей. Если у вас всякий раз для этого пишется новый код, вместо DRY, то боюсь, что на выходе вы получаете такой цирк, что бедные ваши пользователи. Либо вы очень любите копи-пасту, особенно её рефакторинг.

                        • Druu
                          /#10707476 / +1

                          > И не нужно для этого 1000 action-ов.

                          Конечно же, не нужно. Просто делаете, как выше указано, один экшен с редьюсером вида x => return x.payload. А потом к хренам выкидываете редакс :)

                          Неужели вы думаете, что кто-то в здравом уме и твердой памяти пишет по 1000 экшонов? Конечно нет. Просто стейт выносится из редакса.

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

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

                          > Мне кажется вы называете сложным, то, что является простым.

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

                          С другой стороны, есть интерфейсы и проще твиттера. Тогда не то что редакс — наверное, и реакт не нужон :)

                          • Chamie
                            /#10707566 / +1

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

                            • Druu
                              /#10707610

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

                              Так представление разное (информация-то разная).

                  • mayorovp
                    /#10707288 / +1

                    А что не так с разной бизнес-логикой? Какая-то религия мешает проверять в редусерах или мидлварях не только свойство type, но еще и какое-нибудь field?

                    • Druu
                      /#10707344

                      Бизнес-логика в миддлеварях — это лучшее, что я слышал, вы сейчас сделали мой день.

        • TheShock
          /#10707420 / +1

          У меня сейчас под носом довольно сложное приложение. Ради интереса посмотрел, frontend-js файлы: 37k lines, 425 files

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

          • faiwer
            /#10707446

            Ну не 5k, 10-15 было бы. Просто потому, что я очень сильно озаботился тем, чтобы всё было компактным. Скажем у меня нет эти констант, про которые вы пишете (они автоматически генерируются). Файлов очень похожих друг на друга тоже 0. Я очень не люблю копи-пасту. Так что раздутость кода обусловлена тем, что action-ы таки писать приходится, и то, что immutable-code очень громоздкий и малопонятный. Вот именно за счёт reducer-ов я мог бы ужать десяток тысяч. А ну и propTypes (я пока не использую TS).


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

            • Druu
              /#10707514

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

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

              • faiwer
                /#10707526

                Если я выкину Redux, то я потеряю помимо его недостатков, ещё и все его преимущества. Разве это не очевидно?

                • TheShock
                  /#10707542 / +1

                  А какие преимущества редакс? Опишите, пожалуйста. Я просто особых не заметил, пока полгода с ним писал. Может лучше подыскать нормальные инструменты без его недостатков? MobX тот же

                  • faiwer
                    /#10707570

                    Не имею ничего против MobX. И хочу попробовать в каких-нибудь будущих проектах. В сравнении с Knockout я уже писал неоднократно. Redux для меня как глоток свежего воздуха. Особенно мне нравится, что я могу получить от пользователя store с последними 50 действиями в redo-undo стеке, списком воспроиведённых action-ов и отловить даже самый сложный баг за конечное время. Что столкнувшись с чем-то очень необычным, я зная, что тут почти всё детерминировано до мелочей, едва ли встречу какой-нибудь плавающий баг или нестандартное поведение. Преимущества выражаются именно на большой кодовой базе, когда начинаешь забывать всё. Ну и тестировать одно удовольствие.

                    • TheShock
                      /#10707618

                      Простите, на 10к строк кода — далеко не большая кодовая база. И как раз на большой кодовой базе значительно сильнее бьют его недостатки. Ведь сложность кода с его количеством растет геометрически, а не линейно, следовательно гнилое поощрение редаксом копипасты сказывается на коде геометрически плохо. На сколько я помню, в редаксе undo нету, только redo до необходимого шага с инишиал стейта, а это, как и все остальное вами описанное, давно делается в OOP паттерном Команда (в нем, правда и undo легко добавляется)

                      • faiwer
                        /#10707644

                        На сколько я помню, в редаксе undo нету

                        У меня есть. Такие вещи именно на redux реализуются более чем элементарно. В моём случае это довольно сложный редактор, там redo-undo это прямо кнопки на панели.


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


                        Ну и никто не будет её реализовывать просто так (без необходимости). В redux это просто на сдачу даётся. Скажем redux dev tools умеет redo-undo просто автоматически.


                        По поводу 10k — ну сколько успел за 12 месяцев написать, столько успел. Sorry что не оправдал ожиданий :) До этого был как раз Knockout.

                        • mayorovp
                          /#10707672

                          На самом деле, то что у вас redo-undo так хорошо удалось сделать — это тоже удача ваш опыт.

                          В redux никто не застрахован от того что в сторе окажется нечто, что не следует восстанавливать после undo или redo. Или наоборот, то, что следует восстанавливать — окажется засунуто в локальный стейт компонента. А то и вообще часть состояния может оказаться на бакэнде…

                          • faiwer
                            /#10707682

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

                            • mayorovp
                              /#10707690

                              Там все очень просто если с самого начала постулировать эти самые undo или redo. А вот чтобы взять чужую программу, и добавить undo и redo в нее — тут никакой redux не поможет.

                              • faiwer
                                /#10707712

                                С этим соглашусь. Однако в случае redux приложения это будет сделать в стократ проще, чем в случае knockout приложения. Второе скорее всего придётся просто выкинуть.

                                • mayorovp
                                  /#10707764

                                  Неа, не придется :-) Там все тоже очень просто (если, опять-таки, с самого начала делать с прицелом на undo/redo).


                                  Очень помогает библиотека knockout-mapping. Если завернуть вызов ko.mapping.toJS в ko.computed — получим желанную историю состояний.


                                  А вызов ko.mapping.fromJS поможет восстановить сохраненное состояние.

                          • nsinreal
                            /#10708384

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

                            Или наоборот, то, что следует восстанавливать — окажется засунуто в локальный стейт компонента
                            А вот этого быть не должно изначально. Потому что стейт компонента легко теряется даже без undo/redo (if). И довольно странно видеть вместе глобальный стор, который такие вещи решает, и локальный стейт компонента.

                            А то и вообще часть состояния может оказаться на бакэнде…
                            А это интересно. Расскажи поподробнее.

                            • mayorovp
                              /#10708390

                              А что подробнее? Гугл-доки видели? :-)

                              • nsinreal
                                /#10708432

                                Только что специально проверил. Если открыть документ в новой вкладке, то там не работает undo.
                                А revision history имеет вообще свой отдельный UI который позволяет сделать предпросмотр и кнопочку «Restore this version».
                                Так что пример не очень

                                • mayorovp
                                  /#10708446

                                  А вы уверены, что это именно так и должно быть, а не гугл неосилил правильную реализацию? :-)

                                  • nsinreal
                                    /#10708454

                                    С точки зрения UX это вполне обоснованное решение. Ctrl+z для локальных недавних изменений, чтобы поправить маленький косячек. Для всего остального — симулируем систему контроля версий.

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

                                    • mayorovp
                                      /#10708456

                                      Мне тоже интересен этот вопрос :-)

                                      Утверждается что redux в этом как-то поможет...

                                      • nsinreal
                                        /#10708482

                                        Очевидно, что на каждую типичную задачу можно найти такие выдроченные условия, что типичное решение не подойдет. Redux позволяет легко ввести типичное undo/redo. Вы же предлагаете сделать undo/redo между разными репликами, в разное время и с условием только частичной подгрузки данных.

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

                                        Ладно-ладно. Это все равно легкая задача, но требует привлечения бекенда.

                            • TheShock
                              /#10708500

                              А вот этого быть не должно изначально. Потому что стейт компонента легко теряется даже без undo/redo (if). И довольно странно видеть вместе глобальный стор, который такие вещи решает, и локальный стейт компонента.

                              Это, кстати, одна из проблем Редакса. Так можно использовать setState? Или нельзя? Такое впечатление, что фанаты говорят то одну, то другую мысль зависимо от того, что выставит идол в лучшем виде в данном случае. Вот пример, где меня убеждали, что «использовать setState» — это правильный путь при использовании редакса:
                              habrahabr.ru/post/328152/#comment_10207500

                              • nsinreal
                                /#10708582

                                Можно использовать setState. Нельзя использовать setState.

                                It depends. Выбираете вы. Огребаете вы. Такова идеология, стоящая за redux.

                                Мне лично просто странно видеть сообщение в духе «никто не гарантирует, что я буду использовать redux для персистентного состояния, поэтому redux не решает проблем с персистентным состоянием». Ну Ыыы же, разве нет?

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

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

                                • TheShock
                                  /#10711132

                                  Вы прочитайте коммент, что я дал и проблему, которую создает редакс и скажите, как ее решить без setState. Интересно же)

                                  • nsinreal
                                    /#10711244 / -2

                                    Краткая инструкция:

                                    1. Не используйте redux как API внутри универсальных компонентов;
                                    2. Не используйте setState как API внутри универсальных компонентов;
                                    3. В Props ожидайте методы, которые читают состояние и которые пишут состояние;
                                    4. Напишите пару говноврапперов над универсальным компонентом для хипстеров: один с setState, другой с примитивным redux. Второй можно не писать.


                                    Теперь объясню почему у вас вылезла проблема. Redux-way — это сохранять стейт, чтобы он не потерялся. Но у end-developer уже есть свое видение того как должен храниться стейт. Эти видения не могут быть всегда совместимыми.

                                    Проблема интеграции чужих говен модулей/систем вечна. И пока что никто не знает как её решить оптимальным образом.

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

                                    P.S: Мою краткую инструкцию можно распечатать и использовать в качестве туалетной бумаги. Ибо я не пишу универсальные компоненты.

                                    • TheShock
                                      /#10711268 / +1

                                      Окей, 1-3 пункты — очевидны.

                                      другой с примитивным redux

                                      И как его написать? В этом же и вопрос. Просто отвечать на вопрос «как написать» — «примитивно напишите» очень странно.

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

                                      Вот я и спрашиваю — может я неправ и есть путь написать хорошую либу? Или жизнь под редакс подразумевает копание в говне и никаких альтернатив?

                                      • nsinreal
                                        /#10711496

                                        Нууу, очень странный вопрос. Вы приписываете эту проблему redux, а эта проблема проявляется при многих видах интеграции.

                                        Либо вы волевым решением навязываете end-user ваше видение того как хранится стейт, либо отдаете это на откуп ему.

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

                                        И как его написать? В этом же и вопрос. Просто отвечать на вопрос «как написать» — «примитивно напишите» очень странно.
                                        Под «примитивным redux» я подразумевал решение, которое просто работает и которое написано как вам нравится. А если кому-то не нравится как конкретно оно работает, то он может взять и написать под себя используя универсальный компонент.

                                        Впрочем вы можете попытаться экспортировать не просто reducer, а high-order-reducer который можно параметризовать маппингом get/set state. Это в теории должно дать возможность приаттачивать состояние к другой части дерева.

                                        Но тут будет много проблем:
                                        — можно хранить стейт как raw objects, ImmutableJS, seamless immutable, mori — в общем так, как не хочет end-user;
                                        — приаттачивать состояние к другой части стейта может акнуться, потому что другой код может затирать нахер стейт аккордиона.

                                        У меня вопрос: почему вы не любите нормализованный стейт?

        • mayorovp
          /#10707506

          Для сравнения, до этого я долго работал с большим приложением на Knockout-е. Я мог несколько дней убить просто, чтобы хотя бы воспроизвести баг, описанный пользователем. Последовательность отработки различных deferred и пр. observable и impure функции приводили порой к настолько сложным ситуациям, что в части случаев приходилось цеплять нелепые костыли (т.к. за разумное время решить проблему не представлялось возможным). И даже вопроизвести некоторые нюансы конкурентной обработки цепочек computed deferred observable на простом примере могло отнять пару часов.

          Только что обратил внимание на этот абзац… Не может ли быть, что вы просто неправильно готовили Knockout?


          Я почему-то очень часто встречаю подобное в коде коллег: там, где можно было бы использовать один pureComputed и возвращать значение через return — зачем-то делается связка computed + observable.


          Хотя с deferred согласен, это в Knockout прямо-таки беда. Не зря в MobX всяческие задержки оставили только в реакциях.

          • faiwer
            /#10707546

            Не может ли быть, что вы просто неправильно готовили Knockout?

            Именно так. Правильно готовить Knockout с первого раза может только ну очень продвинутый инженер, имеющий подобный опыт. Я о том и пишу. Что Knockout заставляет строить архитектуру большого приложения самостоятельно. Практически без каких-либо ограничений. Нет, кажется, даже никакого knockout-way. А так как на тот проект я пришёл не с нуля, то мне пришлось поддерживать все те архитектурные ошибки, которые там были заложены. И на тот момент я не понимал их. Спустя 3 года пришло понимание, как нужно было делать по уму.


            А redux-react оказался сильно проще. И redux-way расписан в мелочах в тысячах статей.


            Отдельно забыл сказать, что работал я начал с ним 4-5 лет назад, и тогда ряда вещей в библиотеке просто не было. К примеру pureComputed-ов вроде не было. Компонент не было. Мало что было. А кодовая база росла, росла.

            • mayorovp
              /#10707582 / +1

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

              • faiwer
                /#10707596

                Сейчас из каждого утюга просто идёт пиар-компания Redux и React. Доходит до того, что собеседоваться приходят люди, которые не знают азов JS, но уже что-то "мартышат" на React и хотят большую з\п.

  10. amakhrov
    /#10706844 / +1

    На приведенном примере todo-приложения я не увидел, чем же это проще, чем redux. Такие же вью, такие же модели (action-creator + reducer = model).
    На самом деле, модель даже получилась сложнее: TodoItem знает про TodoList (чтобы правильно обработать todoItem.remove()). Если один item начнет входить в несколько списков, логика модели начнет необоснованно усложняться (о эти чудные велосипеды для синхронизации нескольких коллекций в backbone, создания зависимых моделей, аггрегирующих данные из нескольких "первичных" моделей).


    А еще редакс решает очень частую проблему: дочернему компоненту могут понадобиться дополнительные данные, которых нет у родителя. С редаксом это решается путем оборачивания ребенка в connect() — прозрачно для родителя.
    Как это решается в случае с MVC? Лично у меня это также было одной из основных проблем с (моим) backbone-кодом — каждый раз городил огород либо с жесткой зависимостью от глобального синглтон-инстанса модели, либо пробрасывал дополнительные данные по всему дереву сверху вниз.

    • Druu
      /#10706972

      > На приведенном примере todo-приложения я не увидел, чем же это проще, чем redux.

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

      > Как это решается в случае с MVC?

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

  11. IvanNochnoy
    /#10706944

    В сотый раз сморю на картинки с презентации Flux, и не могу понять, в чем проблема с MVC? Я двя дня копал тему в интерненте, все объсняют, как все полохо в MVC ссылаясь на эту картинку со стрелочками, объясняя на примере с зайчиками и даже с задачей о передвижении мебели. Но нигде, я подчеркиваю, нигде нет примера кода, в котором реально видна проблема!

    И еще вот что интересно: почему в презентации Flux и Википедии схемы MVC выглядят совершенно по разному?

    • TheShock
      /#10707516

      Презентация Флакс — это ведь обычная грязная маркетинговая ложь для глуповатых покупателей? В чем манипуляция? В количестве. НА MVC слайде изображено 7 моделей и 7 вьюшек. А на Flux слайде — одна «модель» и одна вьюшка. Неужели Флакс вводит какую-то магию, которая позволяет обойтись одной вьюшкой? Нет, зато эта ложь выглядит круто и ее можно использовать в статьях) А вот как слайд флакса должен выглядеть на самом деле:

      • faiwer
        /#10707580

        А что не так с этой схемой? Если к ней присмотреться, то всё хорошо. Особенно если вместо Flux взять Redux и 10 сторов объединить в один. Ну и View-и выстроить в древовидном виде. А action-ы в виде списка.


        Эти схемы не показывают реальных проблем подхода. Т.к. эти проблемы сложно нарисовать на схеме :)

        • mayorovp
          /#10707606

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

        • TheShock
          /#10707636 / +2

          А что не так с этой схемой?

          Так именно, что ничего) Как и со схемой MVC. Просто они ничего не показывают. Очевидно, что это просто грязные манипуляции с визуализацией данных от пропагандистов Флакса.

  12. IvanNochnoy
    /#10707726 / +1

    Дело в том, что схема Flux подозрительно напоминает… MVC. Не зная, тонкостей реализации, непонятно, в чем разница. MVC может быть двух крайних типов — Pull — когда Model (Store) сообщает о том, что изменения просто произошли, не конкретизируя, что именно изменилось, а View затем вытаскивает эти изменения, и Push — когда Model (Store) конкрено и подробно сообщает о том, какие именно изменения произошли, а View просто изменяется в ответ. Похоже, что коллеги из Facebook открыли Америку, реализовав Pull MVC и придумав ему новое название. По крайней мере, на картинке это выглядит именно так.

    Что же конкретно по релизации, то есть отличия: в классическом MVC программист сам уведомляет об изменениях, релизуя шаблон Наблюдатель, а в Flux, в ответ на сигнал об изменениях проводится сравение с копией Shadow DOM, почти Dirty Checking. Но это тот же MVC. Это было сделано по причине того, что культура JavaScript-программистов не готова к тому, что нужно использовать свойства вместо публичных полей. Что ж, возможно, это и к лучшему, но все равно не понятно, зачем Pull MVC надо было переименовывать во Flux? В конечном итоге, мы избавились от бойлерплейта в одном месте, но получили его в другом.

  13. nsinreal
    /#10708576

    Я почитал комментарии и не могу определить куда бы засунуть этот текст. Поэтому будет комментарий top-level. Половина проблем с redux начинается с того, что люди переносят свое понимание с flux на redux и с MVC на redux.

    Где вы описываете бизнес-логику в MVC? Правильный ответ (нынче): в Model. Вики правда считает, что можно и в модели, и в контроллерах. Но судя по всему современные интерпретации MVC подразумевают, что Model — это не только данные и работа с ними, но еще и бизнес-логику (имеется в виду что слои DAL и BAL вместе называются Model).

    Где вы описываете бизнес-логику в redux? Правильный ответ: а хуй его знает, мы сами не знаем, разгребайте как хотите, мы будем вас гуру считать. См. Where should my “business logic” go?

    И вот когда вы хотите прикрутить к redux какую-либо бизнес-логику — вы огребаете, потому что никто не знает как правильно это сделать. Потому что redux изначально придуман не для этого.

    • TheShock
      /#10708578

      Но вы, видимо, знаете где же ее хранить?

      • nsinreal
        /#10708584

        Буду честен: в душе не представляю.

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

    • Chamie
      /#10709856

      Пойду писать публиковать свой фреймворк для чистых асинхронных action generator'ов в качестве бизнес-логики.

      • Chamie
        /#10709902

        Чистых опционально чистых action generator'ов action-list generator'ов.

      • nsinreal
        /#10710024

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

        Большинство подобных попыток обладают одной проблемой: они пытаются примешать бизнес-логику к persistence, тогда как в идеале business logic layer должен скрывать детали persistence (сюда включается вариант с делегированием по соглашению).

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

        Выбор как-бы не очень хороший.

      • nsinreal
        /#10710026

        Бизнес-логика не только меняет данные, а еще и читает их. Одних action generatorов вам не хватит

        • Chamie
          /#10710108

          Читает — в смысле, из стора? Так давать им getState через middleware.

          • nsinreal
            /#10711624

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