web-dev-qa-db-ja.com

コンポーネントはどのネストレベルでFluxのストアからエンティティを読み取る必要がありますか?

Fluxを使用するようにアプリを書き直していますが、ストアからデータを取得する際に問題があります。私にはたくさんのコンポーネントがあり、それらはたくさんネストしています。それらのいくつかは大きく(Article)、いくつかは小さくシンプルです(UserAvatarUserLink)。

コンポーネント階層のどこでStoresからデータを読み取るべきかについて、私は苦労してきました。
2つの極端なアプローチを試しましたが、どちらも非常に気に入りました。

すべてのエンティティコンポーネントが独自のデータを読み取ります

Storeからのデータを必要とする各コンポーネントは、エンティティIDのみを受け取り、それ自体でエンティティを取得します。
たとえば、ArticleにはarticleIdが渡され、UserAvatarおよびUserLinkには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を子に強制的に渡す必要があります。 )。

すべてのデータはトップレベルで一度読み取られ、コンポーネントに渡されます

バグを追跡するのにうんざりしていたとき、私は検索するすべてのデータを最上位に置こうとしました。ただし、一部のエンティティでは複数レベルのネストがあるため、これは不可能であることが判明しました。

例えば:

  • Categoryには、そのカテゴリに貢献する人々のUserAvatarsが含まれます。
  • Articleには複数のCategoryが含まれる場合があります。

したがって、Articleのレベルでストアからすべてのデータを取得する場合、次のことが必要になります。

  • ArticleStoreから記事を取得します。
  • CategoryStoreからすべての記事のカテゴリを取得します。
  • UserStore;から各カテゴリの貢献者を個別に取得します。
  • どういうわけか、すべてのデータをコンポーネントに渡します。

さらにイライラするのは、深くネストされたエンティティが必要なときはいつでも、ネストの各レベルにコードを追加してそれをさらに渡す必要があることです。

まとめ

どちらのアプローチにも欠陥があるようです。この問題を最もエレガントに解決するにはどうすればよいですか?

私の目標:

  • ストアには、異常な数のサブスクライバーがいるべきではありません。親コンポーネントが既にUserLinkをリッスンしている場合、UserStoreがリッスンするのは愚かなことです。

  • 親コンポーネントがストアからオブジェクトを取得した場合(例:user)、ネストされたコンポーネントがそれを再度取得する必要はありません。小道具で渡すことができるはずです。

  • 関係の追加または削除が複雑になるため、最上位のすべてのエンティティ(関係を含む)を取得する必要はありません。ネストされたエンティティが新しい関係を取得するたびに、すべてのネストレベルで新しい小道具を導入したくありません(たとえば、カテゴリはcuratorを取得します)。

87
Dan Abramov

私が着いたアプローチは、各コンポーネントがIDでなくそのデータを小道具として受け取るようにすることです。ネストされたコンポーネントに関連するエンティティが必要な場合、それを取得するのは親コンポーネント次第です。

この例では、Articleにはオブジェクト(おそらくarticleまたはArticleListによって取得される)であるArticlePage propが必要です。

Articleは記事の著者のUserLinkUserAvatarもレンダリングしたいので、UserStoreをサブスクライブし、author: UserStore.get(article.authorId)をその状態に保ちます。次に、このthis.state.authorUserLinkUserAvatarをレンダリングします。さらに伝えたい場合は、できます。このユーザーを再度取得する必要のある子コンポーネントはありません。

繰り返します:

  • コンポーネントが小道具として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>
    )
  }
});

これにより、最も内側のコンポーネントが馬鹿になりますが、トップレベルのコンポーネントで地獄を複雑にすることはありません。

36
Dan Abramov

ほとんどの人は、階層の最上部近くのコントローラービューコンポーネントで関連するストアを聞くことから始めます。

後で、多くの無関係な小道具が階層を介していくつかの深くネストされたコンポーネントに引き継がれているように見えるとき、一部の人々はそれが良いアイデアだと判断しますより深いコンポーネントがストアの変更をリッスンできるようにします。これにより、コンポーネントツリーのこのより深いブランチが対象としている問題ドメインのカプセル化が改善されます。これを賢明に行うための良い議論があります。

ただし、常に最上位でリッスンし、すべてのデータを単純に渡すことを好みます。ストアの状態全体を単一のオブジェクトとして階層全体に渡すこともありますが、これを複数のストアに対して行います。したがって、ArticleStoreの状態のプロップとUserStoreの状態のプロップなどがあります。深くネストされたコントローラービューを避けることで、データの単一のエントリポイントが維持され、統一されます。データフロー。そうしないと、複数のデータソースがあり、デバッグが困難になる可能性があります。

この戦略では型チェックはより難しくなりますが、ReactのPropTypesを使用して、property-as-propの「形状」または型テンプレートを設定できます。参照: https://github.com/facebook/react/blob/master/src/core/ReactPropTypes.js#L76-L91http://facebook.github.io/ react/docs/reusable-components.html#prop-validation

店舗間で店舗間でデータを関連付けるロジックを配置する場合があることに注意してください。したがって、あなたのArticleStorewaitFor()UserStoreであり、getArticles()を介して提供するすべてのArticleレコードに関連するユーザーを含めることができます。ビューでこれを行うことは、ビューレイヤーにロジックをプッシュするように聞こえますが、これは可能な限り避けるべきプラクティスです。

transferPropsTo()を使用したいと思われるかもしれませんが、多くの人はこれを好んでいますが、読みやすさ、したがって保守性のためにすべてを明示的に保つことを好みます。

FWIW、私の理解では、David NolenはOmフレームワーク( ややFlux-compatible である)と同様のアプローチを取り、ルートノード上のデータの単一のエントリポイントを使用することです-Fluxの同等物はすべてのストアをリッスンする1つのコントローラービューのみを持つようにします。これは、shouldComponentUpdate()と、===を使用して参照によって比較できる不変のデータ構造を使用することで効率的になります。不変のデータ構造については、Davidの mori またはFacebookの immutable-js をチェックアウトしてください。 Omについての私の限られた知識は、主に JavaScript MVCフレームワークの未来

36
fisherwebdev

私の解決策ははるかに簡単です。独自の状態を持つすべてのコンポーネントは、ストアと会話したり聞いたりすることができます。これらは非常にコントローラーのようなコンポーネントです。状態を維持せず、レンダリングだけを行うより深いネストされたコンポーネントは許可されません。彼らは、非常にビューのような純粋なレンダリングの小道具のみを受け取ります。

このように、すべてがステートフルコンポーネントからステートレスコンポーネントに流れます。ステートフルカウントを低く保つ。

あなたの場合、Articleはステートフルであり、したがってストアと対話し、UserLinkなどはレンダリングするだけなので、propとしてarticle.userを受け取ります。

1
Rygu

2つの哲学で説明されている問題は、単一のページアプリケーションに共通しています。

このビデオで簡単に説明します: https://www.youtube.com/watch?v=IrgHurBjQbg and Relay( https://facebook.github.io/relay =)は、あなたが説明するトレードオフを克服するためにFacebookによって開発されました。

リレーのアプローチはveryデータ中心です。これは、「サーバーへの1つのクエリでこのビューの各コンポーネントに必要なデータだけを取得するにはどうすればよいですか?」という質問に対する答えです。また、Relayは、コンポーネントが複数のビューで使用されている場合、コード全体でほとんど結合しないようにします。

Relayがオプションではない場合、「すべてのエンティティコンポーネントが独自のデータを読み取る」ことは、あなたが説明する状況に対して、より良いアプローチのようです。 Fluxの誤解が店だと思います。ストアの概念は、モデルまたはオブジェクトのコレクションが保持される場所ではありません。ストアは、ビューがレンダリングされる前にアプリケーションがデータを置く一時的な場所です。それらが存在する本当の理由は、異なるストアに入るデータ全体の依存関係の問題を解決することです。

Fluxが指定していないのは、ストアがモデルの概念とオブジェクトのコレクション(バックボーンなど)にどのように関係するかです。その意味で、一部の人々は、ユーザーがブラウザを開いたままにする間、フラッシュしない特定のタイプのオブジェクトのコレクションを置く場所を実際にフラックスストアにしていますが、フラックスを理解すると、それはストアではありませんあるはずです。

解決策は、ビューをレンダリングするために必要なエンティティ(および潜在的にそれ以上)が格納され、更新された状態を維持する別のレイヤーを用意することです。このレイヤーがモデルとコレクションを抽象化する場合、サブコンポーネントが独自のデータを取得するために再度クエリを実行する必要がある場合、問題はありません。

0
Chris Cinelli