Тема: React'а тред
Показать сообщение отдельно
  #43 (permalink)  
Старый 07.03.2015, 19:11
sinistral
Посмотреть профиль Найти все сообщения от melky
 
Регистрация: 28.03.2011
Сообщений: 5,418

Сообщение от Gozar Посмотреть сообщение
Есть две архитектуры flux(store, action, component, dispatcher). Одна зависима от другой. Это значит что пока 1 не обновиться, 2 не должен обновляться.
Как организовать внутри одного диспетчера я понимаю(ставим waitFor), а между двумя как построить взаимосвязь недогоняю.

Как организовать эту зависимость с диспетчерами?
Добро пожаловать на грабли событийного программирования! С моделями было проще, да?)

Flux должен быть один. Dispatcher - тоже. http://fluxxor.com/images/flux-complex.png

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

1. Асинхронные действия. Из action возвращаешь Promise. Когда он выполнится, выполняешь другое действие
2. waitFor. Это, можно сказать, сахар над Store.on('change'). Все просто - в хранилище A выполняешь waitFor('B', callback). Когда вызовится B.emit('change'), выполнится и переданный callback
3. Недокументированный API. Хранилища наследуются от EventEmitter. У него есть привычный всем метод once (колбек на один раз).
4. componentDidUpdate(prevProps, prevState) внутри Controller Component. Проверяем, изменились ли данные в новом состоянии. Если да - отсылаем action.

Теперь обо всём по порядку.

1. Асинхронные действия

добавляем в actions параметр callback, или возвращаем Promise.

Это идет в разрез с идеей Flux. Это не стоит использовать (имхо)

2. waitFor

waitFor мне неплохо вмазал (шо аж день не отпускало), так что, думаю, следует хорошо пояснить этот момент

Я использовал Fluxxor, так что его и буду описывать. Документация по Store.waitFor

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

A - master Store
B - slave Store


Сналача, регистрируем обработчик изменения через вызов нужного action (напр. Flux.actions.b.waitForAUpdate)
Далее, выполняем изменение хранилища A (напр. Flux.actions.a.setItem)

И приходим к проблеме. Вызов этого дела выглядит так:
Flux.actions.b.waitForAUpdate()
Flux.actions.a.setItem('yolo')


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

тут либо можно изгальнутся костылем, пустив обновление состояния A через таймаут:
Flux.actions.b.waitForAUpdate()
setTimeout( () => Flux.actions.a.setItem('yolo') )


либо вернуть из создателя действия (ActionCreator, лол) обещание, которое содержит в себе таймаут:
Flux.actions.b.waitForAUpdate().then( () =>
  Flux.actions.a.setItem('yolo')
)


Исходник для Flux.actions.b.waitForAUpdate получается таким:

Файл flux/actions/b.js:
export default {
  waitForAUpdate() {
    return Promise.delay().then(() => 
      this.dispatch('WAIT_FOR_A_STORE')
    )
  },
}

(Promise.delay - это нестандартный метод, из библиотеки bluebird)

Для сравнения, создатель действия без обещания - такой:
Файл flux/actions/b.js:
export default {
  waitForAUpdate() {
    this.dispatch('WAIT_FOR_A_STORE')
    // и чё, кода действие выполнится то?
  },
}


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

3. Недокументированный API.

У нас ведь Flux, так? значит, все действия и хранилища видны всему.

Вкратце - одноразово подписываемся на событие изменения, и из него уже дальше пляшем
Flux.stores('A').once('change', () => {
  // хранилище изменило состояние
  const AState = Flux.stores('A').getState()
  Flux.actions.b.doSimethingWith(AState)
})

Flux.actions.a.setItem('yolo')


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

4. componentDidUpdate(prevProps, prevState)

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

Состояние хранилищ передается controller component, который расфасовывает полученные данные вниз по дереву компонентов через свойства (props)

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

componentDidUpdate, как вы знаете, вызывается после того, как у компонента сменились свойства или состояние. Ну и измениться они могут, в случае controller component, из-за смены состояния одного из прослушиваемых хранилищ

выглядит это так:

A - master Store
B - slave Store


Пусть компонент копирует состояния хранилищ в пространства имен :
// это - слушалка события 'change' у двух наших хранилищ
onSomeStoreChanged() {
  const A = Flux.stores('A').getState()
  const B = Flux.stores('B').getState()

  return { A, B }
}

// состояние хранилища A будет в this.state.A
// для B  - соотв.


отсылаем данные хранилищу A:
Flux.actions.A.setItem('yolo')


внутри CDU, если что-то изменилось, и это состояние хранилища А, то нужно обновить данные в В:
componentDidUpdate(prevProps, prevState) {
  if (this.state.A !== prevState.A) {
    Flux.actions.b.updateFromItem(this.state.A.item)
  }
}


ну, или можно использовать CWU (цепочка реакций должна пойти быстрее):
componentWillUpdate(nextProps, nextState) {
  if (this.state.A !== nextState.A) {
    Flux.actions.b.updateFromItem(nextState.A.item)
  }
}


А у нас цепочка зависимостей (A -> B -> C)? Тогда:
componentWillUpdate(nextProps, nextState) {
  if (this.state.A !== nextState.A) {
    Flux.actions.b.updateFromItem(nextState.A.item)
  }
  if (this.state.B !== nextState.B) {
    Flux.actions.c.updateFromItem(nextState.B.item)
  }
}


Как-то так ... в общем, у нас есть перенос логики обновления в компоненты-контроллеры, но для Flux - это нормально (вроде)

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

waitFor обеспечивает такой же функционал (вызывается, когда хранилище заявило об изменении состояния), но как вызвать действие (Action) внутри хранилища (Store)? МБ дело вкуса, но я лучше не буду пихать всякие actions.yolo() внутри чистенького Store.

Ну и еще - разделение логики обновления данных от самого обновления данных

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

Всё вышесказанное является огромнейшим ИМХО. Если есть предложения, буду рад их услышать.

Последний раз редактировалось melky, 07.03.2015 в 19:35.
Ответить с цитированием