在什么样的嵌套层次上,组件应该从Flux中的Stores中读取实体?

我正在重写我的应用程序来使用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> ) } }); 

这种方法的缺点:

  • 有100个组件可能订阅商店是令人沮丧的;
  • 由于每个组件都独立地检索数据,所以很难跟踪数据的更新方式和顺序
  • 即使你已经拥有了一个实体,你也不得不把它的ID传给孩子,孩子会再次检索它(否则打破一致性)。

所有数据在顶层被读取一次并传递到组件

当我厌倦了追踪错误的时候,我试图把所有的数据都放在顶层。 然而,这被certificate是不可能的,因为对于一些实体我有几个层次的嵌套。

例如:

  • Category包含UserAvatar Category UserAvatar的用户的UserAvatar ;
  • 一篇Article可能有几个Category

因此,如果我想在一篇Article的层次上检索商店中的所有数据,我需要:

  • 从文章ArticleStore检索文章;
  • CategoryStore检索所有文章的CategoryStore ;
  • 分别从UserStore检索每个类别的贡献者;
  • 以某种方式将所有数据传递给组件。

更令人沮丧的是,无论何时我需要一个深度嵌套的实体,我都需要将代码添加到每个嵌套级别,以进一步传递它。

加起来

两种方法似乎都有缺陷。 我如何最优雅地解决这个问题?

我的目标:

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

  • 如果父组件已经从商店(例如user )检索到一些对象,我不希望任何嵌套的组件必须再次获取它。 我应该可以通过道具来传递它。

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

大多数人通过听取层级顶部附近的控制器视图组件中的相关商店来开始。

后来,当很多不相关的道具通过层次结构传递给一些深度嵌套的组件时, 有些人会决定让一个更深层的组件倾听店内的变化是一个好主意。 这为组件树的这个更深的分支所关注的问题域提供了更好的封装。 明智地做这件事,有很好的理由。

不过,我宁愿总是在顶部听,只是传递所有的数据。 我有时甚至会把商店的整个状态作为一个单一的对象传递给整个层次结构,我会为多个商店做到这一点。 所以我会有一个支持UserStore的状态,另一个用于UserStore的状态等等。我发现,避免深度嵌套的控制器视图维护数据的单一入口点,统一数据stream。 否则,我有多个数据源,这可能变得很难debugging。

使用这种策略,types检查更加困难,但是您可以使用React的PropTypes为大对象作为道具设置一个“形状”或types模板。 请参阅: https : //github.com/facebook/react/blob/master/src/core/ReactPropTypes.js#L76-L91 http://facebook.github.io/react/docs/reusable-components.html#prop -validation

请注意,您可能需要将商店之间的数据关联逻辑放在商店本身。 因此,您的UserStore可能会waitFor() UserStore ,并通过getArticles()包含相关的用户与它提供的每个Articlelogging。 在你的观点中这样做听起来像把逻辑推入视图层,这是一种你应该尽可能避免的做法。

你可能也想使用transferPropsTo() ,许多人都喜欢这样做,但为了可读性和可维护性,我更愿意保持一切。

FWIW,我的理解是,David Nolen采用类似的方法,在其根节点上具有单个数据入口点的Om框架(与Flux兼容 ),在Flux中相当于只有一个控制器视图听所有的商店。 这通过使用可以通过引用与===进行比较的shouldComponentUpdate()和不可变数据结构来实现。 对于不可变的数据结构,检出David的mori或Facebook的不可变js 。 我对Om的有限知识主要来自JavaScript MVC框架的未来

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

在我们的例子中, Article应该有一个article支柱,这是一个对象(大概是由ArticleListArticlePage检索的)。

因为Article也想呈现UserLinkUserAvatar作为文章的作者,它将订阅UserStore并保留author: UserStore.get(article.authorId)处于其状态。 然后UserAvatar使用this.state.author呈现UserLinkthis.state.author 。 如果他们希望进一步把它传下去,他们可以。 没有子组件需要再次检索这个用户。

重申:

  • 任何组件都不会收到ID作为道具; 所有组件接收它们各自的对象。
  • 如果子组件需要一个实体,则父代有责任检索它并作为道具传递。

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

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

这保持了最内部的组件愚蠢,但并不强迫我们把地狱的顶级组件复杂化。

我的解决scheme要简单得多。 每个有自己国家的组成部分都允许对商店进行谈话和收听。 这些都是非常类似控制器的组件。 更深的嵌套组件,不维护状态,但只是渲染的东西是不允许的。 他们只接受纯粹渲染的道具,非常风景。

这样一切从有状态组件stream入无状态组件。 保持有状态低。

在你的情况下,条款是有状态的,因此与商店和用户链接等的谈话只会呈现,因此它会收到article.user作为道具。

在您的两种哲学中描述的问题是任何单个页面应用程序所共有的。

他们在这个video中简要讨论: https : //www.youtube.com/watch?v=IrgHurBjQbg和继电器( https://facebook.github.io/relay )是由Facebook开发的,以克服您所描述的折衷。

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

如果Relay不是一个选项 ,“所有的实体组件都读取他们自己的数据”对于我描述的情况来说似乎是一个更好的方法。 我认为Flux的误解就是商店。 存储的概念不存在模型或对象集合的地方。 商店是您的应用程序在呈现视图之前放置数据的临时位置。 他们存在的真正原因是为了解决不同商店中数据的依赖问题。

Flux未指定的是商店如何与模型和对象集合(la Backbone)的概念相关联。 从这个意义上讲,有些人实际上是在一个通量存储的地方,把一个特定types的对象集合在一起,而不是一次性刷新用户保持浏览器打开,但据我所知,stream量,这不是一个商店应该是。

解决的办法是在另一个层,在那里你需要呈现视图的实体(可能更多)存储并保持更新。 如果你这个层抽象模型和集合,如果你的子组件不得不重新查询来获取他们自己的数据,那么这个问题就不成问题了。