Предотвращение цепочек событий с асинхронными зависимостями данных
Диспетчер Flux Facebook явно запрещает ActionCreators отправлять другие ActionCreators. Этот restriciton, вероятно, является хорошей идеей, поскольку он не позволяет вашему приложению создавать цепочки событий.
Это, однако, становится проблемой, как только у вас есть магазины, содержащие данные от асинхронных ActionCreators, которые зависят друг от друга. Если CategoryProductsStore
зависит от CategoryStore
, похоже, не существует способа избежать цепочек событий, не прибегая к отсрочке последующего действия.
Сценарий 1:
Магазин, содержащий список продуктов в категории, должен знать, из какого идентификатора категории он должен получать продукты.
var CategoryProductActions = {
get: function(categoryId) {
Dispatcher.handleViewAction({
type: ActionTypes.LOAD_CATEGORY_PRODUCTS,
categoryId: categoryId
})
ProductAPIUtils
.getByCategoryId(categoryId)
.then(CategoryProductActions.getComplete)
},
getComplete: function(products) {
Dispatcher.handleServerAction({
type: ActionTypes.LOAD_CATEGORY_PRODUCTS_COMPLETE,
products: products
})
}
}
CategoryStore.dispatchToken = Dispatcher.register(function(payload) {
var action = payload.action
switch (action.type) {
case ActionTypes.LOAD_CATEGORIES_COMPLETE:
var category = action.categories[0]
// Attempt to asynchronously fetch products in the given category, this causes an invariant to be thrown.
CategoryProductActions.get(category.id)
...
Сценарий 2:
Другой сценарий заключается в том, что дочерний компонент монтируется в результате изменения Store и его componentWillMount
/componentWillReceiveProps
пытается получить данные через асинхронный ActionCreator:
var Categories = React.createClass({
componentWillMount() {
CategoryStore.addChangeListener(this.onStoreChange)
},
onStoreChange: function() {
this.setState({
category: CategoryStore.getCurrent()
})
},
render: function() {
var category = this.state.category
if (category) {
var products = <CategoryProducts categoryId={category.id} />
}
return (
<div>
{products}
</div>
)
}
})
var CategoryProducts = React.createClass({
componentWillMount: function() {
if (!CategoryProductStore.contains(this.props.categoryId)) {
// Attempt to asynchronously fetch products in the given category, this causes an invariant to be thrown.
CategoryProductActions.get(this.props.categoryId)
}
}
})
Есть ли способы избежать этого, не прибегая к отсрочке?
Ответы
Ответ 1
Всякий раз, когда вы извлекаете состояние приложения, вы хотите получить это состояние непосредственно из Магазинов с помощью методов getter. Действия - это объекты, которые информируют магазины. Вы могли бы думать о них как о желании изменить состояние. Они не должны возвращать какие-либо данные. Они не являются механизмом, по которому вы должны получать состояние приложения, а просто изменяете его.
Итак, в сценарии 1 getCurrent(category.id)
- это то, что должно быть определено в Хранилище.
В сценарии 2 это похоже на то, что вы столкнулись с проблемой инициализации данных Store. Обычно я обрабатываю это (в идеале), получая данные в хранилищах перед рендерингом корневого компонента. Я делаю это в модуле начальной загрузки. В качестве альтернативы, если это абсолютно необходимо для асинхронизации, вы можете создать все, чтобы работать с чистым списком, а затем повторно отобразить после того, как магазины отвечают на действие INITIAL_LOAD.
Ответ 2
Для сценария 1:
Я бы отправил новое действие из самого представления, поэтому будет запускаться цикл действий action → dispatcher → store → .
Я могу представить, что вашему представлению нужно получить список категорий, а также показывать по умолчанию список продуктов первой категории.
Таким образом, этот взгляд будет реагировать на изменения в ClassStore в первую очередь. Как только список категорий загружен, активируйте новое действие, чтобы получить продукты первой категории.
Теперь это сложная часть. Если вы сделаете это в прослушивателе изменений представления, вы получите исключение инварианта, поэтому здесь вам нужно дождаться, когда полезная нагрузка первого действия будет полностью обработана.
Один из способов решения этой проблемы - использовать тайм-аут для прослушивателя изменений вида. Что-то похожее на то, что объясняется здесь:
https://groups.google.com/forum/#!topic/reactjs/1xR9esXX1X4, но вместо отправки этого действия из хранилища вы сделаете это из представления.
function getCategoryProducts(id) {
setTimeout(() => {
if (!AppDispatcher.isDispatching()) {
CategoryProductActions.get(id);
} else {
getCategoryProducts(id);
}
}, 3);
}
Я знаю, это ужасно, но, по крайней мере, у вас не будет хранилищ цепочки действий или логики домена, протекающих к создателям действий. При таком подходе действия "запрашиваются" из представлений, которые действительно нуждаются в них.
Другим вариантом, который я не пробовал честно, является прослушивание события DOM после заполнения компонента со списком категорий. В этот момент вы отправляете новое действие, которое вызовет новую цепочку "Flux". Я на самом деле думаю, что это опрятно, но, как сказано, я еще не пробовал.