避免具有异步数据依赖性的事件链

时间:2014-09-16 04:27:50

标签: reactjs reactjs-flux

Facebook Flux调度员explicitly prohibits ActionCreators from dispatching other ActionCreators。这种限制可能是一个好主意,因为它会阻止您的应用程序创建事件链。

只要您的Stores包含来自彼此依赖的异步ActionCreator的数据,这就成了问题。如果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: 另一种情况是,由于商店更改而导致子组件及其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)
    }
  }
})

有没有办法避免这种情况而不诉诸推迟?

2 个答案:

答案 0 :(得分:3)

每当您检索应用程序的状态时,您希望使用getter方法直接从Stores检索该状态。操作是通知商店的对象。您可以将它们视为状态变化的请求。他们不应该返回任何数据。它们不是您应该检索应用程序状态的机制,而只是更改它。

因此,在方案1 中,getCurrent(category.id)应该在商店中定义。

方案2 中,听起来您遇到了Store数据初始化的问题。我通常通过(理想情况下)在呈现根组件之前将数据放入存储中来处理此问题。我在bootstrapping模块中执行此操作。或者,如果这绝对需要异步,您可以创建所有内容以使用空白平板,然后在商店响应INITIAL_LOAD操作后重新渲染。

答案 1 :(得分:0)

对于方案1:

我会从视图本身发送新动作,因此新的动作 - &gt;调度员 - &gt;商店 - &gt;视图循环将触发。

我可以想象您的视图需要检索类别列表,并且默认情况下还必须显示第一类产品列表。

因此该视图将首先对更改con CategoryStore 做出反应。加载类别列表后,触发新操作以获取第一类产品。

现在,这是棘手的部分。如果在视图的更改侦听器中执行此操作,您将获得一个不变的异常,因此您必须等待第一个操作的有效负载完全处理。

解决此问题的一种方法是在视图的更改侦听器上使用超时。类似于这里解释的东西: https://groups.google.com/forum/#!topic/reactjs/1xR9esXX1X4但您不是从商店调度操作,而是从视图中执行操作。

function getCategoryProducts(id) {
setTimeout(() => {
    if (!AppDispatcher.isDispatching()) {
        CategoryProductActions.get(id);
    } else {
        getCategoryProducts(id);
    }
}, 3);
}

我知道,这太可怕了,但至少你不会将行动或领域逻辑泄漏给行动创造者。使用这种方法,操作将从实际需要它们的视图中“请求”。

另一个我没有诚实尝试过的选项是,一旦填充了带有类别列表的组件,就会监听DOM事件。在那一刻,您将发送新动作,这将触发新的“Flux”链。我实际上认为这个更整洁,但正如所说,我还没有尝试过。