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

IT技术 reactjs reactjs-flux
2021-05-03 17:57:12

Facebook Flux 调度器明确禁止 ActionCreators 调度其他 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 尝试通过异步 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个回答

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

所以在场景 1 中getCurrent(category.id)应该在 Store 上定义一些东西。

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

对于场景 1:

我会从视图本身调度新的动作,所以一个新的动作 -> 调度器 -> 存储 -> 视图周期将被触发。

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

因此该视图将首先对CategoryStore 的更改做出react品类列表加载完毕后,触发新的Action,获取第一品类的产品。

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

解决此问题的一种方法是在视图的更改侦听器上使用超时。类似于此处解释的内容:https : //groups.google.com/forum/#!topic/reactjs/1xR9esXX1X4但不是从商店分派动作,而是从视图中进行。

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

我知道,这很可怕,但至少您不会将存储链接操作或域逻辑泄露给操作创建者。使用这种方法,操作是从实际需要它们的视图中“请求”的。

另一个我没有尝试过的选项是在填充了具有类别列表的组件后侦听 DOM 事件。在那一刻,你调度了一个新的动作,它将触发一个新的“Flux”链。我实际上认为这个更整洁,但如上所述,我还没有尝试过。