避免具有异步数据依赖关系的事件链
Posted
技术标签:
【中文标题】避免具有异步数据依赖关系的事件链【英文标题】:Avoiding event chains with asynchronous data dependencies 【发布时间】:2014-11-09 17:21:22 【问题描述】:Facebook Flux 调度程序explicitly prohibits ActionCreators from dispatching other ActionCreators。这种限制可能是个好主意,因为它会阻止您的应用程序创建事件链。
但是,一旦您的 Store 包含来自相互依赖的异步 ActionCreators 的数据,这就会成为一个问题。如果CategoryProductsStore
依赖于CategoryStore
,则似乎没有办法在不推迟后续操作的情况下避免事件链。
场景 1: 包含某个类别中的产品列表的商店需要知道它应该从哪个类别 ID 获取产品。
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
attempts to fetch data via an asynchronous 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,我将这种逻辑放在动作创建者自己中,这样存储只响应数据的变化。在存在异步逻辑的情况下,动作创建者有时会将多个动作分派到商店。我遇到了场景 #2,要么切换到DidMount
(在异步数据加载的情况下),要么偶尔推迟到 setTimeout
。
@BrandonTilley 我已经澄清了这两个示例,在这两种情况下,用于获取类别中产品的 ActionCreator 都会触发异步 API 操作。
@SimenBrekken 你的问题解决了吗?请问***.com/questions/32537568/…可以看这里吗?
【参考方案1】:
每当您检索应用程序的状态时,您都希望使用 getter 方法直接从 Store 中检索该状态。 Actions 是通知 Store 的对象。您可以将它们视为更改状态的请求。他们不应该返回任何数据。它们不是您应该用来检索应用程序状态的机制,而只是更改它。
所以在场景 1 中,getCurrent(category.id)
应该在商店中定义。
在场景 2 中,您似乎遇到了 Store 数据初始化的问题。我通常通过(理想情况下)在渲染根组件之前将数据放入存储中来处理这个问题。我在引导模块中执行此操作。或者,如果这绝对需要异步,您可以创建所有内容以使用空白板,然后在 Store 响应 INITIAL_LOAD
操作后重新渲染。
【讨论】:
我已经阐明了这两个示例,在这两种情况下,ActionCreator 获取类别中的产品都会触发异步 API 操作。因此,在场景 #1 中,我首先从我的 API 中获取类别列表,然后通过 API 获取该类别中的产品。至于场景#2,我做同样的事情,但只是在组件已安装并需要数据时。我不知道组件何时会被挂载,因此无法将数据输入根组件。 场景 1:产生 LOAD_CATEGORIES_COMPLETE 类型动作的动作创建者在哪里?似乎对 API 的调用应该转移到该动作创建者。我不清楚 LOAD_CATEGORY_PRODUCTS 是否有用。 场景 2:您的视图正在管理商店的数据。让商店管理自己的数据。 我认为在哪里可以调用 API 以获取更多数据存在一些混淆。在商店里这样做是可以的。重要的是简单地确保当数据返回时,您然后通过操作进入流程,而不是直接修改存储。这确保了 store 可以控制自己,并且任何其他 store 都可以通过 Flux 方式获知相同的新数据。 场景 #1:我目前正在返回一个承诺,其中 CategoryProductActions.getComplete 是 LOAD_CATEGORIES_COMPLETE 操作创建者。场景#2:所以你的意思是我应该在给定类别中询问产品,而商店本身调用动作创建者?【参考方案2】:对于场景 1:
我会从视图本身调度新的动作,因此将触发一个新的动作 -> 调度程序 -> 存储 -> 视图 循环。
我可以想象您的视图需要检索类别列表,并且默认情况下它还必须显示第一个类别的产品列表。
因此该视图将首先对 CategoryStore 的更改做出反应。加载分类列表后,触发新的 Action 获取第一个分类的产品。
现在,这是棘手的部分。如果你在视图的更改监听器中这样做,你会得到一个不变的异常,所以这里你必须等待第一个动作的负载被完全处理。
解决此问题的一种方法是在视图的更改侦听器上使用超时。类似于此处解释的内容: https://groups.google.com/forum/#!topic/reactjs/1xR9esXX1X4 但不是从存储中调度操作,而是从视图中执行。
function getCategoryProducts(id)
setTimeout(() =>
if (!AppDispatcher.isDispatching())
CategoryProductActions.get(id);
else
getCategoryProducts(id);
, 3);
我知道,这很可怕,但至少您不会有存储链接操作或域逻辑泄漏给操作创建者。使用这种方法,操作是从实际需要它们的视图中“请求”的。
另一个我没有尝试过的选项是,一旦包含类别列表的组件被填充,就监听 DOM 事件。在那一刻,你调度了一个新的动作,这将触发一个新的“通量”链。我其实觉得这个更整洁,但正如我所说,我还没有尝试过。
【讨论】:
以上是关于避免具有异步数据依赖关系的事件链的主要内容,如果未能解决你的问题,请参考以下文章