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

Dan*_*mov 88 javascript reactjs reactjs-flux

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

我一直在努力应对组件层次结构中应该从Stores读取数据的地方.
我尝试了两种极端的方法,我都不喜欢这两种方法:

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

需要来自Store的某些数据的每个组件只接收实体ID并自行检索实体.
例如,Article传递articleId,UserAvatarUserLink传递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>
    )
  }
});
Run Code Online (Sandbox Code Playgroud)

这种方法的缺点:

  • 让100个组件可能订阅商店令人沮丧;
  • 由于每个组件独立检索数据,因此很难跟踪数据的更新方式和顺序.
  • 即使您可能已经拥有处于州的实体,您也不得不将其ID传递给子级,子级将再次检索它(否则会破坏一致性).

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

当我厌倦了追踪错误时,我试图将所有数据检索放在最顶层.然而,事实证明这是不可能的,因为对于某些实体,我有几个级别的嵌套.

例如:

  • A Category包含UserAvatar对该类别做出贡献的人;
  • 一个Article可能有几个Category秒.

因此,如果我想从商店级别检索商店中的所有数据Article,我需要:

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

更令人沮丧的是,每当我需要一个深度嵌套的实体时,我需要在每个嵌套级别添加代码以进一步传递它.

加起来

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

我的目标:

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

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

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

Dan*_*mov 37

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

在我们的例子中,Article应该有一个articleprop是一个对象(可能是由ArticleListor 检索ArticlePage).

因为Article也想渲染UserLinkUserAvatar文章的作者,它将订阅UserStore并保持author: UserStore.get(article.authorId)其状态.然后,它将使UserLinkUserAvatarthis.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>
    )
  }
});
Run Code Online (Sandbox Code Playgroud)

这使得最内层的组件变得愚蠢,但并没有迫使我们使顶层组件变得复杂.


fis*_*dev 36

大多数人通过在层次结构顶部附近的控制器视图组件中监听相关存储来开始.

后来,当看起来很多不相关的道具通过层次结构传递到一些深层嵌套的组件时,有些人会认为让更深层的组件监听商店中的变化是个好主意.这为组件树的这个更深层次的分支提供了更好的问题域封装.明智地做这件事有很好的论据.

但是,我更喜欢总是在顶部听,简单地传递所有数据.我有时甚至会将整个商店状态作为单个对象传递给层次结构,我会为多个商店执行此操作.所以我会有一个支持ArticleStore状态,另一个支持UserStore状态,等等.我发现避免深层嵌套的控制器视图可以保持数据的单一入口点,并统一数据流.否则,我有多个数据源,这可能变得难以调试.

使用此策略进行类型检查更加困难,但您可以使用React的PropTypes为大对象-prop支持设置"形状"或类型模板.请参阅:https : //github.com/facebook/react/blob/master/src/core/ReactPropTypes.js#L76-L91 http://facebook.github.io/react/docs/reusable-components.html#prop -validation

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

您可能也很想使用transferPropsTo(),很多人喜欢这样做,但我更喜欢将所有内容都保持清晰,以便于阅读和维护.

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

  • 根据您正在构建的应用程序类型,只要您开始在屏幕上显示不直接属于同一根的更多"小部件",保留一个单一入口点就会受到伤害.如果您强行尝试以这种方式执行,那么根节点将承担太多责任.KISS和SOLID,即使它是客户端. (7认同)
  • 谢谢你的详细解答!我确实考虑过传递商店状态的整个快照.然而,这可能会给我一个性能问题,因为每次更新UserStore都会导致屏幕上的所有文章被重新呈现,而PureRenderMixin将无法分辨哪些文章需要更新,哪些文章不需要更新.至于你的第二个建议,我也考虑过这个问题,但如果文章的用户在每个getArticles()调用中被"注入",我就看不出如何保持文章引用相等.这将再次潜在地给我带来性能问题. (3认同)
  • 是的,无论如何,我只需要在我确定**perf问题时就这样做,对于每分钟最多更新几次的商店来说不应该这样.再次感谢你! (3认同)