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

Предположим, у меня есть приложение, использующее Flux.

В большинстве руководств по Flux говорится, что ваш Components должен прослушивать событие change на Store и соответствующим образом отображать данные. Этот подход отлично подходит для небольших и простых случаев использования, однако у меня есть проблема в чуть более сложном сценарии:

У меня есть список клиентов, которых я могу отфильтровать по городу проживания (Вашингтон, Нью-Йорк и т. д.). Этот список доступен по URL-адресу /customers/by-city и может быть представлен следующим кодом:

var CustomersList = React.createClass({
 
  getInitialState() {
    return {
      city: 'Washington',
      customers: []
    };
  },
 
  componentWillMount: function() {
    CustomersActions.retrieveForCity(this.state.city);
    CustomersStore.on('change', this.handleNewData);
  },

  componentWillUnmount: function() {
    CustomersStore.off('change', this.handleNewData);
  },

  render: function() {
    return <FilteredTable filters={['city']}
                          onCityChange={this.handleCityChange} 
                          data={this.state.customers} />;
  },

  handleCityChange: function(state) {
    CustomersActions.retrieveForCity(this.state.city);
    this.setState({city: city});
  },
  
  handleNewData: function(customers) {
    this.setState({customers: customers});
  }

});

Каждый раз, когда я хочу отфильтровать по другому городу, запрос AJAX выдается CustomersActions, ответ идет на Dispatcher. Затем CustomersStore берет его, сохраняет как data и выдает событие change. Почти стандартный материал Flux.

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

  • Я фильтрую таблицу по San Francisco
  • Я передумал, и до того, как запрос AJAX будет завершен, я нажимаю на какую-то навигационную ссылку (обрабатывается реакцией, без перезагрузки страницы), и я оказываюсь на другом URL-адресе, например. /customers/shortlisted. По этому URL-адресу доступен еще один компонент: ShortlistedCustomers.
  • ShortlistedCustomers похож на CustomersList - для отображения данных он отправляет запрос ajax через CustomerActions и прослушивает событие change на CustomersStore
  • Теперь хитрость - второй запрос ajax выполняется раньше первого, выдаются два события change, и пользователь получает неправильные данные в своей таблице.

Как решить это "правильно"? Меня интересует самое простое возможное решение, может быть, что-то, что порекомендовали сами ребята из FB? Я понимаю, что мог бы, например. отменить первый AJAX или обрабатывать ответы только в том порядке, в котором были отправлены эти запросы, но это быстро выйдет из-под контроля.

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


person Adam Zielinski    schedule 05.11.2015    source источник
comment
Я перехожу к другому компоненту в своем приложении --- как этот переход представлен на самом деле?   -  person zerkms    schedule 05.11.2015
comment
Я обновил свой вопрос - это может быть что-то вроде реактивного маршрутизатора или подобного; страница не перезагружается, если ты это имеешь в виду   -  person Adam Zielinski    schedule 05.11.2015
comment
В вашем текущем коде вы начинаете с объекта с двумя атрибутами, а затем заменяете его объектом с одним атрибутом. первый аргумент setState() — это diff, а не новое состояние; поэтому он сливается с существующим состоянием   -  person Adam Zielinski    schedule 05.11.2015
comment
facebook.github.io/react/docs/component-api.html setState() Performs a shallow merge of nextState into current state   -  person Adam Zielinski    schedule 05.11.2015
comment
О боже, я всегда думал, что он каждый раз заменяет весь объект :-S извинения   -  person zerkms    schedule 05.11.2015


Ответы (3)


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

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

CustomersStore прослушивает отправленное действие запроса API

  1. Отправляется действие, указывающее, что запрос API был отправлен
  2. CustomersStore очищает свое состояние, когда это действие отправляется

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

CustomersStore хранит ответы в сегментах

  1. Данные, запрошенные для конечной точки /by-city, сохраняются в CustomersStore под хешем byCity.
  2. Компоненты для /by-city теперь получают доступ к своим данным, указав этот хеш, например, CustomersStore.getData('byCity')
  3. Данные для /shortlisted сохраняются и извлекаются таким же образом.

Теперь вы можете использовать некоторую логику кэширования.

Создайте действие для сброса CustomersStore

  1. Когда CustomersStore получает это действие «Сброс», он очищает сохраненные данные.
  2. Компоненты /by-city и /shortlisted отправляют действие «Сброс» в течение соответствующей функции жизненного цикла.

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

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

person Toby Liu    schedule 05.11.2015
comment
Подход Buckets имеет смысл, однако он не защищает пользователя от условий гонки одного и того же сегмента, например. Я фильтрую CustomersList, иду куда-то еще и возвращаюсь до того, как первоначальный AJAX завершится. - person Adam Zielinski; 05.11.2015

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

import Reflux from 'reflux';

import Actions from './Actions';
import AddonStore from './Addon.Store';
import MixinStoreObject from './Mixin.Store';

function _GotData(data) { this.data1 = data; BasicStore.trigger('data1'); }

let BasicStoreObject = {
	init() { this.listenTo(AddonStore, this.onAddonTrigger); },
	data1: {},
	listenables: Actions,
	mixins: [MixinStoreObject],
	onGotData1: _GotData,
	onAddonTrigger() { BasicStore.trigger('data2'); },
	getData1() { return this.data1; },
	getData2() { return AddonStore.data2; },
	getData3() { return this.data3; }
}
const BasicStore = Reflux.createStore(BasicStoreObject);
export default BasicStore;

import React from 'react';

import BasicStore from './../flux/Basic.Store';

let AppCtrlSty = {
	height: '100%',
	padding: '0 10px 0 0'
}

const getState = () => {
	return {
		Data1: BasicStore.getData1(),
		Data2: BasicStore.getData2(),
		Data3: BasicStore.getData3()
	};
};

class AppCtrlRender extends React.Component {
 	render() {
		let data1 = JSON.stringify(this.state.Data1, null, 2);
		let data2 = JSON.stringify(this.state.Data2, null, 2);
		let data3 = JSON.stringify(this.state.Data3, null, 2);
		return (
			<div id='AppCtrlSty' style={AppCtrlSty}>
				React 1.4 ReFlux with SuperAgent<br/><br/>
				Data1: {data1}<br/><br/>
				Data2: {data2}<br/><br/>
				Data3: {data3}<br/><br/>
			</div>
		);
	}
}

export default class AppCtrl extends AppCtrlRender {
	constructor() {
		super();
		this.state = getState();
	}

	componentDidMount() { this.unsubscribe = BasicStore.listen(this.storeDidChange.bind(this)); }
	componentWillUnmount() { this.unsubscribe(); }
	storeDidChange(id) {
		switch (id) {
			case 'data1': this.setState({Data1: BasicStore.getData1()}); break;
			case 'data2': this.setState({Data2: BasicStore.getData2()}); break;
			case 'data3': this.setState({Data3: BasicStore.getData3()}); break;
			default: this.setState(getState());
		}
	}
}

person J. Mark Stevens    schedule 05.11.2015

ShortListedCustomers и CustomersList должны иметь/'брать свои данные' из разных хранилищ [или отдельные свойства из общего хранилища]

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

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

person Bruno Grieder    schedule 09.11.2015