组件应在什么嵌套级别从Flux商店读取实体?

我正在重写我的应用程序以使用Flux,但从商店检索数据时遇到问题。我有很多组件,它们嵌套很多。其中有些是大(Article),有些是小而简单(UserAvatarUserLink)。

我一直在努力在组件层次结构中的哪些位置应该从商店读取数据。
我尝试了两种极端的方法,但我都不喜欢:

所有实体组件都读取自己的数据

需要从Store获得一些数据的每个组件仅接收实体ID,并自行检索实体。
例如,Article被传递articleIdUserAvatarUserLink传递userId

这种方法有几个明显的缺点(在代码示例下讨论)。

var Article = React.createClass({
  mixins: [createStoreMixin(ArticleStore)],

  propTypes: {
    articleId: PropTypes.number.isRequired
  },

  getStateFromStores() {
    return {
      article: ArticleStore.get(this.props.articleId);
    }
  },

  render() {
    var article = this.state.article,
        userId = article.userId;

    return (
      <div>
        <UserLink userId={userId}>
          <UserAvatar userId={userId} />
        </UserLink>

        <h1>{article.title}</h1>
        <p>{article.text}</p>

        <p>Read more by <UserLink userId={userId} />.</p>
      </div>
    )
  }
});

var UserAvatar = React.createClass({
  mixins: [createStoreMixin(UserStore)],

  propTypes: {
    userId: PropTypes.number.isRequired
  },

  getStateFromStores() {
    return {
      user: UserStore.get(this.props.userId);
    }
  },

  render() {
    var user = this.state.user;

    return (
      <img src={user.thumbnailUrl} />
    )
  }
});

var UserLink = React.createClass({
  mixins: [createStoreMixin(UserStore)],

  propTypes: {
    userId: PropTypes.number.isRequired
  },

  getStateFromStores() {
    return {
      user: UserStore.get(this.props.userId);
    }
  },

  render() {
    var user = this.state.user;

    return (
      <Link to='user' params={{ userId: this.props.userId }}>
        {this.props.children || user.name}
      </Link>
    )
  }
});

这种方法的缺点:

  • It's frustrating to have 100s components potentially subscribing to Stores;
  • It's hard to keep track of how data is updated and in what order because each component retrieves its data independently;
  • Even though you might already have an entity in state, you are forced to pass its ID to children, who will retrieve it again (or else break the consistency).

All data is read once at the top level and passed down to components

When I was tired of tracking down bugs, I tried to put all data retrieving at the top level. This, however, proved impossible because for some entities I have several levels of nesting.

For example:

  • A Category contains UserAvatars of people who contribute to that category;
  • An Article may have several Categorys.

Therefore if I wanted to retrieve all data from Stores at the level of an Article, I would need to:

  • Retrieve article from ArticleStore;
  • Retrieve all article's categories from CategoryStore;
  • Separately retrieve each category's contributors from UserStore;
  • Somehow pass all that data down to components.

Even more frustratingly, whenever I need a deeply nested entity, I would need to add code to each level of nesting to additionally pass it down.

Summing Up

Both approaches seem flawed. How do I solve this problem most elegantly?

My objectives:

  • Stores shouldn't have an insane number of subscribers. It's stupid for each UserLink to listen to UserStore if parent components already do that.

  • 如果父组件已经从存储中检索到某个对象(例如user),我不希望任何嵌套的组件都必须再次获取它。我应该能够通过道具传递它。

  • 我不必在顶层获取所有实体(包括关系),因为这会使添加或删除关系复杂化。我不想每次嵌套实体获得新关系(例如category获得curator时都在所有嵌套级别引入新道具

十三Davaid2020/03/12 18:15:54

我的解决方案要简单得多。每个具有自己状态的组件都可以交谈和收听商店。这些是非常类似于控制器的组件。不允许嵌套更深层的嵌套组件,这些组件不能保持状态,而只能渲染内容。他们仅收到用于纯渲染的道具,非常类似于视图。

这样,一切都从有状态组件流入无状态组件。保持状态计数低。

在您的情况下,Article将是有状态的,因此仅与商店和UserLink等进行对话,因此它将接收article.user作为道具。

Eva2020/03/12 18:15:54

您的2种方法中描述的问题对于任何单页应用程序都是常见的。

在此视频中简要讨论了它们:https : //www.youtube.com/watch?v=IrgHurBjQbg和Relay(https://facebook.github.io/relay)是Facebook开发的,目的是克服您描述的折衷。

中继的方法非常以数据为中心。它是对以下问题的答案:“如何通过对服务器的一次查询,仅获取该视图中每个组件的所需数据?” 同时,当一个组件在多个视图中使用时,Relay确保您在代码之间几乎没有耦合。

如果不是Relay的选择,那么针对您所描述的情况,“所有实体组件都读取其自己的数据”似乎是更好的方法。我认为Flux的误解是一家商店。存储的概念不应该是保留模型或对象集合的地方。商店是应用程序在呈现视图之前将数据放入其中的临时位置。它们存在的真正原因是为了解决跨不同存储的数据之间的依赖性问题。

What Flux is not specifying is how a store relate to the concept of models and collection of objects (a la Backbone). In that sense some people are actually making a flux store a place where to put collection of objects of a specific type that is not flush for the whole time the user keeps the browser open but, as I understand flux, that is not what a store is supposed to be.

The solution is to have another layer where you where the entities necessary to render your view (and potentially more) are stored and kept updated. If you this layer that abstract models and collections, it is not a problem if you the subcomponents have to query again to get their own data.

理查德阳光2020/03/12 18:15:54

大多数人是从在层级顶部附近的控制器视图组件中收听相关商店开始的。

后来,当似乎许多无关的道具通过层次结构向下传递到某个深层嵌套的组件时,有些人会认为让一个更深的组件侦听商店中的更改是个好主意。这样可以更好地封装问题域,而组件树的这个更深层次的分支就是这个问题域。要明智地进行此操作,有很多理由。

但是,我更喜欢始终在最上面听,并简单地传递所有数据。有时,我什至会考虑存储的整个状态,并将其作为单个对象向下传递到层次结构中,而我将对多个存储执行此操作。因此,我将为ArticleStore的状态提供一个支持,为的状态提供一个支持UserStore,等等。我发现避免深度嵌套的控制器视图将为数据保留单个入口,并统一数据流。否则,我有多个数据源,这可能变得很难调试。

使用这种策略进行类型检查比较困难,但是您可以使用React的PropTypes为“按对象放置大型对象”设置“形状”或类型模板。参见:https : //github.com/facebook/react/blob/master/src/core/ReactPropTypes.js#L76-L91 http://facebook.github.io/react/docs/reusable-components.html#prop -验证

请注意,您可能需要在商店本身中放置在商店之间关联数据的逻辑。所以,你的ArticleStore实力waitFor()UserStore,并且包括与每一个相关用户Article提供通过记录getArticles()在您的视图中执行此操作听起来就像将逻辑推入视图层,这是您应尽可能避免的一种做法。

您可能也很想使用transferPropsTo(),并且许多人都喜欢这样做,但是我更喜欢保持所有内容的明确性,以提高可读性和可维护性。

FWIW,我的理解是David Nolen对其Om框架(在某种程度上与Flux兼容采用了类似的方法,在根节点上只有一个数据入口点-Flux中的等效方法是只有一个控制器视图听所有商店。通过使用shouldComponentUpdate()不可变的数据结构(可以通过引用将其与===进行比较)来提高效率对于不可变的数据结构,请查看David的mori或Facebook的immutable-js我对Om的有限了解主要来自JavaScript MVC框架的未来