СоХабр закрыт.

С 13.05.2019 изменения постов больше не отслеживаются, и новые посты не сохраняются.

H DELETED в черновиках

DELETED

+10
~7400

комментарии (94)

+12
dagen ,  
Вот так незатейливо можно превратить Flux+React в Backbone+vDOM. С основным посылом статьи согласен — конечно же Redux не серебряная пуля.
+1
DarthVictor ,   * (был изменён)
А если еще и оптимизацией заниматься в будущем, потому что иначе никак, то… ууу… целый квест…

В случае react-redux может оказаться достаточно часто обновляемые значения перенести в отдельный редьюсер.
+1
+3 –2
Amareis ,  

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

–2
+4 –6
BuPy7 ,   * (был изменён)
1. Где Вас смутил и какой бойлерплейт?
2. Если проект не на полгода и требует долгосрочной поддержки, то не стоит использовать какие-то библиотеки, ради экономии трех файлов. Особенно, если они влияют на архитектуру. Эта самая библиотека неизвестно сколько времени проживет, как будет поддерживаться, что в ней будут изменять и как. Для меня очень важна отказоустойчивость после обновления. Мир JS очень прогрессивный, что будет дальше модно и что устареет — не предсказать. А архитектурные шаблоны неизменны. В общем, сомнительный выигрыш в подтягивании еще одной зависимости.
+5
+8 –3
gnaeus ,  
Позвольте с Вами не согласиться. У меня сейчас на поддержке находится приложение пятилетней давности с такими вот ручными самописными подписками на события. И если честно, ловля блох в этих Observer-ах отнимает 80% времени на разбирательство с багом.

Не надо так делать! Уж лучше Redux с его бойлерплейтом, зато простой как топор. А еще лучше MobX, где куча проблем уже формализованы и решены за нас оптимальным способом.
–3
+1 –4
BuPy7 ,   * (был изменён)
Ваше заявление звучит, как «у меня была квартира в кирпичном доме, а через пару лет его стало косить, значит, все кирпичные дома плохие, не покупайте в них квартиру». И плюс, чем самостоятельно написанное решение для работы с событиями может отличаться от решения модной библиотеки на GitHub? Все абсолютно зависит от того, как это используется, а не от того, сколько звезд или сколько человек использует данное решение. Можно и с MobX сделать жизнь несладкой.
+6
gnaeus ,  

А кто Вам сказал, что я встречался с ОДНИМ таким приложением? Давайте не будем пользоваться ложными аналогиями.


чем самостоятельно написанное решение для работы с событиями может отличаться от решения модной библиотеки

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


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

А если Ваш велосипед превосходит решение с гитхаба по всем этим показателям — самое время выложить его в OpenSource и написать про это на Хабр =)

–2
BuPy7 ,   * (был изменён)
лучшей архитектурой

Кто сказал?


более продуманным API

С чего Вы взяли?


лучшей документацией (она как минимум есть)

Допустим, но какая она и дает ли она ответа? У Zend Framework тоже есть документация, но люди все равно читают исходники.


меньшей когнитивной нагрузкой на новичков в проекте (не нужно читать исходники реализации)

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


и наконец, большей надежностью

Кто Вам сказал? Почему Вы так уверены, что там нет какой-то заподлянки.


Все ваши утверждения надуманны и никто никаких гарантий не дает.


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

+2
gnaeus ,   * (был изменён)

Ну то есть Вы настолько уверены в себе и Вам кажется, что можете заменить команду fulltime-разработчиков, и десятки пользователей бета-тестеров? Мы же говорим не про проекты уровня left-pad. А про вещи, на которые будет завязана архитектура Вашего приложения. Т.е. взять и выпилить велосипед при обнаружении проблемы не получится.


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


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

0
BuPy7 ,   * (был изменён)
Я смотрю на вещи реально. Если кода на три файла (грубо говоря) — тащить библиотеку нет смысла. В любом случае, чтобы использовать библиотеку, нужно понимать для чего, как и почему. Ведь программисту потом все равно её внедрять и он может внедрить её плохо или хорошо. А там, где он взял её — дело третье. Она должна решать проблему и всего лишь. От неправильного применения или непонимания скаченная библиотека с GitHub не спасает.

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

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

Об этом и речь. Нужно уметь строить архитектуру. Впиханная жестко библиотека также никуда не денется, когда умрет.
+1
staticlab ,  

Вы же сами пишете: «Вам не нужен Redux. Можете пользоваться Flux». Под Flux понимается архитектура или всё-таки библиотека? Если исключительно первое, то почему Mobx пользоваться можно? Если второе, то чем Redux хуже?

0
BuPy7 ,  
Flux — архитектура. Redux — библиотека. Или нет?

Я пишу «Вам не нужен Redux» и пишу варианты, и читатель уже решает сам.

Чем Redux хуже видно из двух других примеров. Если для Вас Redux является великолепным решением — пусть оно так и будет. А чем хуже я уже написал в статье.
0
staticlab ,  

Flux — и архитектура, и библиотека: https://github.com/facebook/flux.

+1
gnaeus ,  

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


Но вернемся к обсерверам. Писать на них каунтер понятно как. А если взять реляционные данные? Ну вот например модель этой статьи — Пост, Комментарии, Пользователи. В Redux я буду использовать нормализованные данные. В MobX — простые объекты, как в ORM на бекэнде. В mobx-state-tree я смогу использовать оба варианта. На уровне Stote удобнее нормализация, а в компонентах — объектное представление.


А вот как реализовать такое на MVVM? Какие понадобятся модели, вью-модели и обсерверы?

0
BuPy7 ,   * (был изменён)

Если в MobX используется, как MV; компоненты, как V; и есть хранилище (M), то все будет аналогично. Большая часть людей использующих MobX и так реализуют MVVM.

0
gnaeus ,  
Я еще не видел подхода, чтобы MobX был в качестве VM, а перед ним использовался еще какой-то слой M. Как правило в качестве модели и выступают структуры данных MobX + какой-то слой сервисов. А в качестве VM здесь разве что компоненты-контейнеры, содержащие «computed» свойства.
0
BuPy7 ,   * (был изменён)

Я не эксперт MobX и никогда не использовал его, видел статью, где делали MVVM с ним. Но в данном случае тогда все плохо, раз так, как Вы говорите. Бизнес-логике в компоненте-контейнере делать нечего. Контейнер-компонент можно использовать, как промежуточную часть между видом и бизнес-логикой, но никак не вместо бизнес-логики. Как это тестировать потом вообще? Имхо, не клева.

0
gnaeus ,   * (был изменён)

Если уж сравнивать подход MobX, с MVVM, то окажется что MVVM — это прошлый век =)


Есть Модель в виде реактивных объектов с данными и сервисов с бизнес-операциями. И есть Представление в виде дерева React-компонентов. В какой-то отдельной сущности ViewModel, которая оповещает компонент об изменениях модели, нет нужды. Потому что данные модели реактивны. Т.е. каждый объект данных сразу является и subject-ом. И они сами оповещают подписчиков о своем изменении.


Ну или можете считать, что вся логика VM реализована в общем виде в HOC observer() из mobx-react.


Кстати, точно так же работает и Vue, и Knockout, и $mol (этот даже вроде круче)

–1
vintage ,  
Не совсем так. В качестве моделей выступают пропсы, что бы в них не передали. В качестве вью-модели — собственно компонент. А в качестве вьюшек — вложенные компоненты. Получается такая фрактальная структура, где каждый компонент выступает как вьюшка вышестоящего компонента и как вью-модель для нижестоящих. Ну а моделью на каждом уровне может выступать вообще всё, что угодно. Вышестоящий компонент, другие вложенные компоненты, сервисы, сторы и тд.
0
gnaeus ,  
Ну наверное. Просто MVC, MVVM — это не священная корова, а всего лишь паттерны проектирования. И похоже, что наступил момент, когда часть паттерна реализуется встроенными средствами языка. Например, каждый раз когда мы передаем лямбда-функцию в Array.prototype.map, мы же не проговариваем про себя, что используем паттерн Стратегия из GoF. И если мы понимаем, как оно работает внутри, зачем пытаться выделить искуственные абстракции?
0
mayorovp ,  

Слой сервисов — это и есть модель в терминах MVVM

+2
morr ,   * (был изменён)
Первый вариант со Smart & Dumb Components в большинстве случаев не работает вообще. Уровней иерархии компонентов в реальном приложении совсем не два, а 5-10, и прокидывать множество колбеков через компоненты всей иерархии замучаешься.

Лично мне redux не зашёл из-за большого объёма boilerplate кода, и в проектах использую связку mobx + mobx-state-tree. Собственно третий вариант автора это почти и есть mobx, только с mobx будет меньше кода и удобнее.
–1
caffeinum ,  
Есть небольшая библиотека, которая убирает из redux бойлерплейт: github.com/pavelivanov/redaction
+5
drai3 ,  
Помимо уже вышеупомянутого mobx есть довольно простой и немногословный React Context, который, как по мне, тоже выглядит проще и понятнее чем третий вариант из статьи.
0
gnaeus ,  

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

+1
drai3 ,  
С моей точки зрения React Context можно и нужно использовать как дополнение к, например, Apollo (если не хочется использовать apollo-link-state) для хранения простых состояний каких-либо фильтров/переключателей. Это избавит вас от своих велосипедов и решит проблему стора средствами самого реакта.

Если действительно нужен гибкий, масштабируемый стор, то mobx/redux или какие-либо event-driven модели конечно же выглядят намного более подходящим решением.
0
gnaeus ,   * (был изменён)

Ну кстати да — вариант. Доменная часть состояния как правило присутствует и на сервере. Поэтому доменная логика отъезжает в мутации GraphQL. А для всякой глобальной презентационной фигни достаточно Component.setState() и React Context.

+2
indestructable ,  

Во флакс-архитектуре (к которой относится и редакс) в сравнении с классической мобх-архитектурой:


(Дисклеймер: многое ниже верно только при использовании саг)


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


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


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


0
gnaeus ,  
В итоге мобх методы превращаются в смесь из бизнес действий и переключения состояния

Ну, на бекэнде эту проблему давно решили разделением на DomainLayer (переключение состояния модели) и BusinessLayer (эффекты). Кто мешает сделать такьв MobX?

0
indestructable ,  

Я имел ввиду ситуацию типа следующей:


  • нужно загрузить данные
  • устанавливаем свойство «идётЗагрузка»
  • отправляем запрос на сервер
  • проверяем ответ
  • ок — делаем что-то и сбрасываем флаг «идётЗагрузка»
  • не ок — делаем ещё что-то и опять же, сбрасываем флаг

Все это — и бизнес действия, и переключение состояния приложения — идёт вперемешку.


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


Редакс отделяет эти переключения флагов и тп в редьюсер, а бизнес логика остаётся в саге.

0
gnaeus ,  

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


yield put({ type: "MY_DATA_LOADING" })

От


MyDataService.setLoading();

?


Разве что в первом случае у нас слабое связывание за счет pub/sub (action/reducers).
Ну так во втором случае мы можем получить объект MyDataService с помощью Dependency Injection.

0
indestructable ,  

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

+1
gnaeus ,   * (был изменён)
А это уже классическое разделение между Pub/Sub и прямым вызовом. У первого большая гибкость, а у второго — предсказуемость. А зависимость-то она не от реализации, а от абстракции. DI же.
0
mayorovp ,  

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


А еще есть вот такая штука в библиотеке mobx-utils:


class Foo {
    @observable todo = null;

    @action doRequest() {
        this.todo = fromPromise(fetch("https://jsonplaceholder.typicode.com/todos/1"));
    }

    get isPending() {
        return this.todo.state == "pending";
    }

    // ...
}

Ну и с чем именно тут перемешана бизнес-логика, по-вашему?

–1
indestructable ,  

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

+1
mayorovp ,  
Тогда приведите эти самые «более сложные случаи».
0
gnaeus ,   * (был изменён)
Отдельно еще хочется сказать про саги. Они хороши с академической точки зрения.

А вот на практике это во-первых бойлерплейт X2. Для каждого действия помимо редуксовских ActionType, ActionCreator, Reducer и mapDispatchToProps, нам нужны еще сага-watcher, регистрация его в корневой саге, сага с логикой, селекторы. И это очень печально. 80% действий в типичном приложении – это изменение поля ввода, показ диалога, переключение вкладки и т.п. Эти действия не должны требовать построения полного цикла обработки события. И в том же MobX это так.

Во-вторых — при чтении кода теряется фокус на бизнес-задаче. Потому что код щедро разбавлен заклинаниями из redux-saga.

А особенно печально, и я часто вижу это, когда саги тащат в приложения уровня лендинга или единственного раздела классического multipage сайта. Там и Redux-то часто не нужен (о чем и статья). А тут люди вкрячивают саги, потому что это якобы best practices.
+1
indestructable ,  

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

+2
gnaeus ,  

Мне кажется, что redux-saga вообще стоит рассматривать не как библиотеку, а как DSL для сайд-эффектов. И как всякий DSL он отлично выполняет задачи в своей области применения. Лучше, чем например RxJS, который скорее библиотека для того же самого.


Но, как любой новый язык, он требует более глубокого изучения, чем просто библиотека. Отсюда и ощущение "книги заклинаний". Например, я был в шоке от такого (пример из оф. доки):


try {    
  while (true) {
    let seconds = yield take(chan)
    console.log(`countdown: ${seconds}`)
  }
} finally {
  if (yield cancelled()) {
    chan.close()
    console.log('countdown cancelled')
  }    
}

Как вообще код выйдет из while (true) без break и return? Но потом я почитал внимательно доки по генераторам и понял, что все норм. А представьте, что подумает джун увидев такое?


И разумеется, DSL имеет свои границы применимости. Поэтому не надо пихать саги туда, где не ожидаются сложные эффекты. Загрузить данные по клику на кнопку с показом "Loading..." — это не сложный эффект.

+1
indestructable ,  

Ну, собственно, библиотека, предоставляющая подобие DSL для сайд-эффектов, она так себя и позиционирует.

+2
funca ,  

Этот код ставит в ступор не только джуна. Код хорошо описывает заморочку: саги в самом деле концептуально как ремонт — они могут не заканчиваться ни когда, их можно лишь прекратить. Если на клиенте с абстракциями в стиле CSP (Communicating Sequential Processes) вполне можно жить, хотя вся эта затея и выглядит как прикол (в конечном счёте страницу перезагрузят), то при рендере на сервере все же удобнее работать с конечными штуками.

0
indestructable ,  

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

0
andreyverbin ,  

При изменении view model хотим рассылать оповещение, потому либо а) генераторы свойств модели, прокси либо б) единый способ установки свойств ala dispatch


Если свойство запускает оповещение, то установка каждого из них может менять подписчиков синхронно. Было бы круто, чтобы вместо этого все могли дождаться окончания изменений и перерисоваться один раз. Кроме того, обработчик изменения firstname может желать делать разные вещи, в зависимости от того, изменился ли lastname вместе с ним. Решения — а) хитрые биндинги и подписки или б) отказ от обновления индивидуальных свойств, ala payload в dispatch.


На моей практике view model проще, до определенного момента, для простых ситуаций всем проще выбрать а). По мере роста сложности и взаимосвязей в UI все больше хочется выбрать б) Количество boilerplate в redux ужасает, явно можно сделать лучше.

0
Tantrido ,  
Вся проблема в том, что никто не объясняет зачем нужен и когда нужен Redux
Ты вот тоже не объяснил! ;)
0
BuPy7 ,   * (был изменён)
Там тонкий намек, что от не использования никто ничего не теряет.
0
staticlab ,  
вызывать экшен, который вызовет обновление хранилища, что вызовет перерисовку всех подписанных контейнеров, а за ними и компонентов

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

0
BuPy7 ,  
Верно, всех подписанных на изменения.
0
staticlab ,  

И в чём разница по сравнению с вашим подходом на обсерверах?

0
BuPy7 ,  
Мне не обязательно отвечать на этот вопрос, но просто задача: у вас три ajax-dropdown с возможностью поиска, с одинаковыми на одном экране данными. Т.е. допустим, три dropdown со списком пользователей, которые дергаются с сервера. Реализуйте, пожалуйста.
0
staticlab ,  

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

0
BuPy7 ,  
Меня больше интересует часть с actions и reducers, как там Вы все организуете, и что будете передавать в компоненты.
0
+1 –1
staticlab ,   * (был изменён)
Код
function findUsers(query) {
  return dispatch => {
    dispatch({
      type: SEARCHING_USERS,
      payload: true
    });

    searchUsers(query)
      .then(foundUsers => {
        dispatch({
          type: SET_FOUND_USERS,
          payload: foundUsers
        });
      })
      .finally(() => {
        dispatch({
          type: SEARCHING_USERS,
          payload: false
        });
      })
  };
}

function reducer(state = initialState, action) {
    switch (action.type) {
    case SEARCHING_USERS:
      return {
        ...state,
        searchingUsers: action.payload
      };

    case SET_FOUND_USERS:
      return {
        ...state,
        foundUsers: action.payload
      };
    default:
      return state;
    }
}

class Dropdown extends Component {
  state = {
    isOpen: false,
    query: ''
  };

  shouldComponentUpdate(nextProps, nextState) {
    return !shallowEqual(nextState, this.state)
      || (this.state.isOpen && (nextProps.foundUsers !== this.props.foundUsers));
  }

  handleSearchChange = (query) => {
    this.setState({ query });
    this.props.onChange(query);
  };
}
0
BuPy7 ,   * (был изменён)

Да, но их три, а не один, а следовательно все, кроме Dropdown умножаем на три.

0
staticlab ,  

Нет. Во-первых, вы не можете использовать все три поиска одновременно. Если же всё-таки нужно использовать независимые хранилища, то просто передаём ключ:


function findUsers(query, key) {
  return dispatch => {
    searchUsers(query)
      .then(foundUsers => {
        dispatch({
          type: SET_FOUND_USERS,
          payload: { foundUsers, key }
        });
      })
  };
}

function reducer(state = initialState, action) {
    switch (action.type) {
    case SET_FOUND_USERS:
      return {
        ...state,
        foundUsers[action.payload.key]: action.payload.foundUsers
      };
}
0
BuPy7 ,   * (был изменён)

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

0
staticlab ,  

А зачем вам их везде таскать в данном случае?

0
BuPy7 ,   * (был изменён)
А как Вы предлагаете мне выполнить запрос для трех разных dropdown и сохранить предыдущее состояние каждого из них в store?
0
staticlab ,  

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

0
staticlab ,   * (был изменён)

Дело в том, что вы пишете:


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

В большинстве случаев достаточно PureComponent.


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

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


А ведь еще нужно и передать все эти данные в экшн! И так по кругу. Представьте форму из 20+ полей и все эти поля нужно постоянно передавать по кругу.

Если используется, например, redux-thunk (а как минимум он будет присутствовать, если есть запросы к серверу), то он передаёт getState в экшен. По кругу пробрасывать не потребуется.


И чем больше приложение, тем глубже и больше дерево редьюсеров

Дерево для того и дерево, чтобы разносить логику.


Я не говорю, что Redux — идеальный подход. У него есть свои недостатки, но они совсем другие.

0
BuPy7 ,   * (был изменён)
Если используется, например, redux-thunk (а как минимум он будет присутствовать, если есть запросы к серверу), то он передаёт getState в экшен. По кругу пробрасывать не потребуется.

Тогда нарушается принципы, которые заложены из Flux: action ->… -> store, и только в этом направлении. Никак иначе. Дергать store-данные из экшена это уже костыль.


image


Я не говорю, что Redux — идеальный подход. У него есть свои недостатки, но они совсем другие.

Я бы с удовольствием почитал Ваши мысли по этому поводу.

+1
staticlab ,  

А асинхронный подход redux-thunk, строго говоря, уже не ложится на Flux-архитектуру, потому что асинхронные экшены там порождаются не из View-слоя. Если же считать, что они всё-таки вызываются View-слоем, только асинхронно, то противоречия нет, потому что View имеет доступ к чтению Store.


Основной принцип Flux всё-таки в том, что View не имеет права непосредственно изменять Store. Эта архитектура отражает более общий принцип CQR (Command-Query Segregation), разделяющий получение данных (Query) и их изменение (Command). В данном случае action creators так же не изменяют данные (это забота редьюсеров), а только запрашивают их. Принцип CQR не нарушается.

0
funca ,  

Thunk может сам играть роль view? В архитектуре же не сказано, что вью одно и оно обязательно реакт. А так, сам по себе реакт это просто функция.

0
staticlab ,  

Теоретически да. И react берёт данные из стора и «асинхронно» диспатчит экшены, и thunk может брать данные из стора и асинхронно диспатчить экшены. Разница только в том, что react получает данные пассивно через connect, а thunk — активно, вызывая getState.

0
babylon ,   * (был изменён)
Дерево для того и дерево, чтобы разносить логику.

Дерево для того и дерево, чтобы разносить диспетчеры .

+1
+2 –1
vintage ,   * (был изменён)
За счет того, что CartPage имеет приоритет выше, чем наши компоненты, мы смогли добиться взаимодействия между ними (компонентами).

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


запросы к API — это, совсем другая ответственность, а следовательно, нам нужно их где-то разместить. Для этого существуют репозитории.
Вот и все. Прозрачно и понятно.

И неправильно.


  1. В случае ошибки при загрузки, приложение повиснет в состоянии ожидания.


  2. При любом ремаунте (а в Реакте они могут происходить по куче безобидных причин) будет происходить повторный запрос данных.


  3. Запросы происходят для каждого места, где эти данные нужны, никакого шаринга.



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

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


Без бойлерплейта, с API — https://codesandbox.io/s/pk19r1ov7m

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


Осторожно, просмотр кода на $mol может вызвать приступ немотивированной агрессии

Компонент с кнопкой:


$my_cart_details $mol_view
    quantity?val 0
    sub /
        <= Increase $mol_button
            click?event <=> increase?event null
            title \Increment

Добавленная логика инкремента:


class $my_cart_details extends $.$my_cart_details {

    increase() {
        this.quantity( this.quantity() + 1 )
    }

}

Компонент со статистикой:


$my_cart_info $mol_view
    sub /
        <= prefix \Quantity: 
        <= quantity 0

Вьюшка приложения (она же "точка входа"):


$my_cart_app $mol_view
    quantity?val 0
    sub /
        <= Info $my_cart_info
            quantity <= quantity
        <= Cart $my_cart_details
            quantity?val <=> quantity?val

Добавленное асинхронное взаимодействие с сервером:


class $my_cart_app extends $.$my_cart_app {

    quantity( next? : number ) {
        return this.$.$mol_http.resource( '/api' ).json( next )
    }

}
0
BuPy7 ,   * (был изменён)
> В случае ошибки при загрузки, приложение повиснет в состоянии ожидания.

Зачем оно там повиснет? Ошибки нужно отлавливать.

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

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

> Запросы происходят для каждого места, где эти данные нужны, никакого шаринга.

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

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

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

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

Это как раз я рассматриваю в конце статьи.

А про $mol ничего не скажу. Не использовал.
+1
vintage ,  
Зачем оно там повиснет? Ошибки нужно отлавливать.

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


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

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


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

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


'Самое веселье начинается, когда оказывается, что для вью-модели требуется несколько моделей.'
Значит, что-то пошло не так.

Что значит не так? Это типичная задача. Например, статья, комментарии и пользователи, как выше отметили.


Это как раз я рассматриваю в конце статьи.

Вы про костыль с ручной подпиской и как обычно забытой отпиской? :-)

0
BuPy7 ,   * (был изменён)
Нужно, но вы это не реализовали.

Есть множество замечаний к коду. И, прошу заметить, я нигде этого не сделал. Как мне кажется, это очевидно. Или нет? =)


Это типичная задача.

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


Вы про костыль с ручной подпиской и как обычно забытой отпиской? :-)

Почему костыль? Что-то начало рендериться, в ViewModel мы подписались на изменения. Если что-то измениться — все обновиться. И отпиской от чего и почему забытой?

+1
vintage ,  
> Как часто Вам требуется общий стор? Мне не особо. Когда это требуется — проблем его добавить нет.

Несколько моделей в одном компоненте — постоянно.

> И отпиской от чего и почему забытой?

Вы подписываетесь, но никогда не отписываетесь, что приводит в лучшем случае к утечкам памяти.
0
BuPy7 ,  
> Несколько моделей в одном компоненте — постоянно.

Интересно.

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

Где я не отписался?
0
vintage ,  
В CartInfoViewModel в конце статьи.
0
BuPy7 ,   * (был изменён)
У нас ведь там замыкание. Всегда актуальное количество будет.
0
vintage ,   * (был изменён)
Ага, и замыкание это будет работать, даже когда пользователь закроет корзину.
0
BuPy7 ,  
Да, верно. Здесь уже нужно плясать от конкретной ситуации. В моем примере надобности в отписке нет. =)
+1
vintage ,  
Почему это? Потому что всё приложение — это два с половиной компонента, которые никогда не анмаунтятся?
0
BuPy7 ,   * (был изменён)
Верно. И, возможно, потому что отписка может быть и не нужна осознанно.
+2
indestructable ,  

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


В более-менее объёмном приложении весь бойлерплейт покрывается фабриками стандартных редьюсеров, саг, селекторов и тп


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


Для уменьшения бойлерплейта с экшенами использую такой подход: https://github.com/rd-dev-ukraine/rd-redux-utils/blob/master/readme.md


Реализация — в десять строк.

0
gnaeus ,  
Это да. Только проблема в том, что каждый избавляется от бойлерплейта по-своему.

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

Теперь каждый новый проект на React — это конструктор из Redux + 10 рандомных библиотек. И комбинации не повторяются =)
0
411 ,  
Поддержу, взять тот же Rails. Бойлерплейт всегда генерируется одинаково. Кому не нужен бойлерплейт, могут ручками написать по-своему.

Да и вообще convention over configuration отличный принцип для фундаментальных библиотек(и не только).
0
funca ,  

Тогда вам в ангуляр :)

+1
411 ,  
Как ни странно при всём при этом реакт мне нравится намного больше. А ангуляр вот совсем не зашёл, нелюбовь с первого взгляда.
+1
indestructable ,  

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

+1
funca ,  

Что вы думаете на счёт хуков в последних версиях react? По-моему они предоставили вполне аккуратный интерфейс, чтобы не так сильно запинаться о сайдэффекты.

–1
staticlab ,  

Все эти хуки — костыли для прикручивания lifecycle к функциональным компонентам.

0
funca ,  

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

0
vintage ,  
Ага, setState и setProps — такие прям функциональные штуки.
0
staticlab ,  

Но хуки только внешне выглядят функциональными, а на самом деле внутри модифицируют компоненты как в ООП.

0
gnaeus ,  
Хуки — они скорее про презентационные сайд-эффекты (императивная работа с DOM). А мы говорим про эффекты бизнес логики (I/O). Конечно, данные можно загружать и из хука, но только если эти данные не нужны в соседних компонентах.
0
mayorovp ,   * (был изменён)
Вот только хуки-то как раз императивные. Обилия функций и лямбд еще недостаточно чтобы код назывался функциональным, в данном случае он скорее стал процедурным.
+1
+2 –1
411 ,  
Вам не нужен Redux

Может лучше сразу было на битву экстрасенсов идти? Смелые заявления без наличия входных данных по задаче.


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

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


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

Как подтверждение второй части.


Но люди, почему-то, ссут подумать своей головой.

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


И сейчас я вам поведаю сказ о том, как жить без Redux.

До его появления все как-то жили, может лучше было провести урок истории?


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


Вообще перед написанием статьи стоит сначала определить для кого она и с какой целью пишется. «Убедить весь мир, что редукс — г-но» — очень плохое решение для этих двух пунктов.


Холиварить не планирую, серебрянной пули всё равно нет.

0
BuPy7 ,  
Заголовок, да, стоит поправить.
+1
BuPy7 ,  
Спасибо за замечания. Убрал кричащие высказывания. Вы правы.
+2
neic ,  
Автор, спасибо тебе, я не один такой :)
Я сам разрабатываю что-то похожее. Я пришел к выводу что ко всему что Вы перечислили необходимо добавить транзакции (как писали выше), например:
```
sync = (isUpdate) => {
this.beginTransaction();
this.dosomething1();
this.dosomething2();

this.dosomethingN();
this.endTransaction();
}
```

Т.е. вызов changeHandler сработает 1 раз вместо N раз
0
miolini ,  
Интересно, как живут без Redux/Flux разработчики iOS/Android/Qt/WPF?