javascript 组件应该在哪个嵌套级别从 Flux 中的 Stores 读取实体?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25701168/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-28 04:52:19  来源:igfitidea点击:

At what nesting level should components read entities from Stores in Flux?

javascriptreactjsreactjs-flux

提问by Dan Abramov

I'm rewriting my app to use Flux and I have an issue with retrieving data from Stores. I have a lot of components, and they nest a lot. Some of them are large (Article), some are small and simple (UserAvatar, UserLink).

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

I've been struggling with where in component hierarchy I should read data from Stores.
I tried two extreme approaches, neither of which I quite liked:

我一直在努力解决组件层次结构中应该从 Stores 读取数据的问题。
我尝试了两种极端的方法,但我都不太喜欢:

All entity components read their own data

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

Each component that needs some data from Store receives just entity ID and retrieves entity on its own.
For example, Articleis passed articleId, UserAvatarand UserLinkare passed userId.

每个需要来自 Store 的数据的组件只接收实体 ID 并自行检索实体。
例如,Article被通过articleIdUserAvatar并且UserLink被通过userId

This approach has several significant downsides (discussed under code sample).

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

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>
    )
  }
});

Downsides of this approach:

这种方法的缺点:

  • 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 orderbecause each component retrieves its data independently;
  • Even though you might alreadyhave an entity in state, you are forced to pass its ID to children, who will retrieve it again (or else break the consistency).
  • 有 100 个组件可能订阅 Stores 令人沮丧;
  • 这是很难掌握的数据如何被更新,以什么顺序,因为每个组件独立获取数据;
  • 即使您可能已经有一个处于状态的实体,您也必须将其 ID 传递给子代,子代将再次检索它(否则会破坏一致性)。

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 Categorycontains UserAvatars of people who contribute to that category;
  • An Articlemay have several Categorys.
  • ACategory包含UserAvatar为该类别做出贡献的人 s;
  • 一个Article可能有几个Categorys。

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

因此,如果我想在 a 级别从 Stores 检索所有数据Article,我需要:

  • 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.
  • 检索文章ArticleStore
  • 检索所有文章的类别CategoryStore
  • 从 中分别检索每个类别的贡献者UserStore
  • 以某种方式将所有这些数据传递给组件。

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 UserLinkto listen to UserStoreif parent components already do that.

  • If parent component has retrieved some object from store (e.g. user), I don't want any nested components to have to fetch it again. I should be able to pass it via props.

  • I shouldn't have to fetch all entities (including relationships) at the top level because it would complicate adding or removing relationships. I don't want to introduce new props at all nesting levels each time a nested entity gets a new relationship (e.g. category gets a curator).

  • 商店不应该有疯狂的订阅者数量。如果父组件已经这样做了,那么每个UserLink人都听是愚蠢的UserStore

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

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

采纳答案by Dan Abramov

The approach at which I arrived is having each components receive its data (not IDs) as a prop. If some nested component needs a related entity, it's up to the parent component to retrieve it.

我到达的方法是让每个组件接收其数据(而不是 ID)作为道具。如果某个嵌套组件需要相关实体,则由父组件来检索它。

In our example, Articleshould have an articleprop which is an object (presumably retrieved by ArticleListor ArticlePage).

在我们的示例中,Article应该有一个article道具,它是一个对象(大概由ArticleList或检索ArticlePage)。

Because Articlealso wants to render UserLinkand UserAvatarfor article's author, it will subscribe to UserStoreand keep author: UserStore.get(article.authorId)in its state. It will then render UserLinkand UserAvatarwith this this.state.author. If they wish to pass it down further, they can. No child components will need to retrieve this user again.

因为Article也想渲染UserLinkUserAvatar对文章的作者,其将认购UserStore,并保持author: UserStore.get(article.authorId)它的状态。然后它将渲染UserLinkUserAvatar使用 this this.state.author。如果他们想进一步传递它,他们可以。没有子组件需要再次检索此用户。

To reiterate:

重申:

  • No component ever receives ID as a prop; all components receive their respective objects.
  • If child components needs an entity, it's parent's responsibility to retrieve it and pass as a prop.
  • 没有任何组件将 ID 作为道具接收;所有组件都接收它们各自的对象。
  • 如果子组件需要一个实体,父组件有责任检索它并作为道具传递。

This solves my problem quite nicely. Code example rewritten to use this approach:

这很好地解决了我的问题。重写代码示例以使用此方法:

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

  propTypes: {
    article: PropTypes.object.isRequired
  },

  getStateFromStores() {
    return {
      author: UserStore.get(this.props.article.authorId);
    }
  },

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

    return (
      <div>
        <UserLink user={author}>
          <UserAvatar user={author} />
        </UserLink>

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

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

var UserAvatar = React.createClass({
  propTypes: {
    user: PropTypes.object.isRequired
  },

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

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

var UserLink = React.createClass({
  propTypes: {
    user: PropTypes.object.isRequired
  },

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

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

This keeps innermost components stupid but doesn't force us to complicate the hell out of top level components.

这使最内部的组件变得愚蠢,但不会迫使我们将顶级组件复杂化。

回答by fisherwebdev

Most people start out by listening to the relevant stores in a controller-view component near the top of the hierarchy.

大多数人首先通过在层次结构顶部附近的控制器视图组件中侦听相关存储。

Later, when it seems like a lot of irrelevant props are getting passed down through the hierarchy to some deeply nested component, some peoplewill decided it's a good idea to let a deeper component listen for changes in the stores. This offers a better encapsulation of the problem domain that this deeper branch of the component tree is about. There are good arguments to be made for doing this judiciously.

后来,当似乎有很多不相关的 props 通过层次结构传递到某个深层嵌套的组件时,有些人会认为让更深层次的组件监听 store 中的变化是个好主意。这为组件树的这个更深分支所涉及的问题域提供了更好的封装。明智地这样做是有充分理由的。

However, I prefer to always listen at the top and simply pass down all the data. I will sometimes even take the entire state of the store and pass it down through the hierarchy as a single object, and I will do this for multiple stores. So I would have a prop for the ArticleStore's state, and another for the UserStore's state, etc. I find that avoiding deeply nested controller-views maintains a singular entry point for the data, and unifies the data flow. Otherwise, I have multiple sources of data, and this can become difficult to debug.

但是,我更喜欢始终在顶部聆听并简单地传递所有数据。有时我什至会获取商店的整个状态并将其作为单个对象通过层次结构向下传递,我将为多个商店执行此操作。所以我会有一个用于ArticleStore状态的道具,另一个用于UserStore状态等。我发现避免深度嵌套的控制器视图维护数据的单一入口点,并统一数据流。否则,我有多个数据源,这会变得难以调试。

Type checking is more difficult with this strategy, but you can set up a "shape", or type template, for the large-object-as-prop with React's PropTypes. See: https://github.com/facebook/react/blob/master/src/core/ReactPropTypes.js#L76-L91http://facebook.github.io/react/docs/reusable-components.html#prop-validation

这种策略的类型检查更加困难,但您可以使用 React 的 PropTypes 为 large-object-as-prop 设置“形状”或类型模板。请参阅:https: //github.com/facebook/react/blob/master/src/core/ReactPropTypes.js#L76-L91 http://facebook.github.io/react/docs/reusable-components.html#prop -验证

Note that you may want to put the logic of associating data between stores in the stores themselves. So your ArticleStoremight waitFor()the UserStore, and include the relevant Users with every Articlerecord it provides through getArticles(). Doing this in your views sounds like pushing logic into the view layer, which is a practice you should avoid whenever possible.

请注意,您可能希望将在商店之间关联数据的逻辑放在商店本身中。因此,您ArticleStore可以waitFor()UserStore相关用户包含在Article它提供的每条记录中getArticles()。在您的视图中执行此操作听起来像是将逻辑推入视图层,这是您应尽可能避免的做法。

You might also be tempted to use transferPropsTo(), and many people like doing this, but I prefer to keep everything explicit for readability and thus maintainability.

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

FWIW, my understanding is that David Nolen takes a similar approach with his Om framework (which is somewhat Flux-compatible) with a single entry point of data on the root node -- the equivalent in Flux would be to only have one controller-view listening to all stores. This is made efficient by using shouldComponentUpdate()and immutable data structures that can be compared by reference, with ===. For immutable data structures, checkout David's morior Facebook's immutable-js. My limited knowledge of Om primarily comes from The Future of JavaScript MVC Frameworks

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

回答by Rygu

My solution is much simpler. Every component that has its own state is allowed to talk and listen to stores. These are very controller-like components. Deeper nested components that don't maintain state but just render stuff aren't allowed. They only receive props for pure rendering, very view-like.

我的解决方案要简单得多。每个具有自己状态的组件都可以与商店交谈和聆听。这些是非常类似于控制器的组件。不允许不维护状态而仅呈现内容的更深嵌套组件。他们只接受纯粹渲染的道具,非常像视图。

This way everything flows from stateful components into stateless components. Keeping the statefuls count low.

通过这种方式,一切都从有状态组件流向无状态组件。保持有状态的计数很低。

In your case, Article would be stateful and therefore talks to the stores and UserLink etc. would only render so it would receive article.user as prop.

在您的情况下,文章将是有状态的,因此与商店和 UserLink 等对话只会呈现,因此它将接收 article.user 作为道具。

回答by Chris Cinelli

The problems described in your 2 philosophies are common to any single page application.

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

They are discussed briefly in this video: https://www.youtube.com/watch?v=IrgHurBjQbgand Relay ( https://facebook.github.io/relay) was developed by Facebook to overcome the tradeoff that you describe.

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

Relay's approach is verydata centric. It is an answer to the question "How do I get just the needed data for each components in this view in one query to the server?" And at the same time Relay makes sure that you have little coupling across the code when a component used in multiple views.

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

If Relay is not an option, "All entity components read their own data" seems a better approach to me for the situation you describe. I think the misconception in Flux is what a store is. The concept of store exist no to be the place where a model or a collection of objects are kept. Stores are temporary places where your application put the data before the view is rendered. The real reason they exist is to solve the problem of dependencies across the data that goes in different stores.

如果 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.

Flux 没有说明的是 store 如何与模型和对象集合的概念相关联(a la Backbone)。从这个意义上说,有些人实际上是在让通量商店成为一个放置特定类型对象集合的地方,这些对象在用户保持浏览器打开的整个时间里都不会齐平,但据我了解,这不是商店应该是。

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.

解决方案是在另一个层中存储渲染视图(以及可能更多)所需的实体并保持更新。如果您在这一层抽象模型和集合,那么如果您的子组件必须再次查询以获取他们自己的数据,这不是问题。