Fluxを使用するようにアプリを書き直していますが、ストアからデータを取得する際に問題があります。私にはたくさんのコンポーネントがあり、それらはたくさんネストしています。それらのいくつかは大きく(Article
)、いくつかは小さくシンプルです(UserAvatar
、UserLink
)。
コンポーネント階層のどこで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>
)
}
});
このアプローチの欠点:
バグを追跡するのにうんざりしていたとき、私は検索するすべてのデータを最上位に置こうとしました。ただし、一部のエンティティでは複数レベルのネストがあるため、これは不可能であることが判明しました。
例えば:
Category
には、そのカテゴリに貢献する人々のUserAvatar
sが含まれます。Article
には複数のCategory
が含まれる場合があります。したがって、Article
のレベルでストアからすべてのデータを取得する場合、次のことが必要になります。
ArticleStore
から記事を取得します。CategoryStore
からすべての記事のカテゴリを取得します。UserStore
;から各カテゴリの貢献者を個別に取得します。さらにイライラするのは、深くネストされたエンティティが必要なときはいつでも、ネストの各レベルにコードを追加してそれをさらに渡す必要があることです。
どちらのアプローチにも欠陥があるようです。この問題を最もエレガントに解決するにはどうすればよいですか?
私の目標:
ストアには、異常な数のサブスクライバーがいるべきではありません。親コンポーネントが既にUserLink
をリッスンしている場合、UserStore
がリッスンするのは愚かなことです。
親コンポーネントがストアからオブジェクトを取得した場合(例:user
)、ネストされたコンポーネントがそれを再度取得する必要はありません。小道具で渡すことができるはずです。
関係の追加または削除が複雑になるため、最上位のすべてのエンティティ(関係を含む)を取得する必要はありません。ネストされたエンティティが新しい関係を取得するたびに、すべてのネストレベルで新しい小道具を導入したくありません(たとえば、カテゴリはcurator
を取得します)。
私が着いたアプローチは、各コンポーネントがIDでなくそのデータを小道具として受け取るようにすることです。ネストされたコンポーネントに関連するエンティティが必要な場合、それを取得するのは親コンポーネント次第です。
この例では、Article
にはオブジェクト(おそらくarticle
またはArticleList
によって取得される)であるArticlePage
propが必要です。
Article
は記事の著者のUserLink
とUserAvatar
もレンダリングしたいので、UserStore
をサブスクライブし、author: UserStore.get(article.authorId)
をその状態に保ちます。次に、このthis.state.author
でUserLink
とUserAvatar
をレンダリングします。さらに伝えたい場合は、できます。このユーザーを再度取得する必要のある子コンポーネントはありません。
繰り返します:
これは私の問題を非常にうまく解決します。このアプローチを使用するように書き換えられたコード例:
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>
)
}
});
これにより、最も内側のコンポーネントが馬鹿になりますが、トップレベルのコンポーネントで地獄を複雑にすることはありません。
ほとんどの人は、階層の最上部近くのコントローラービューコンポーネントで関連するストアを聞くことから始めます。
後で、多くの無関係な小道具が階層を介していくつかの深くネストされたコンポーネントに引き継がれているように見えるとき、一部の人々はそれが良いアイデアだと判断しますより深いコンポーネントがストアの変更をリッスンできるようにします。これにより、コンポーネントツリーのこのより深いブランチが対象としている問題ドメインのカプセル化が改善されます。これを賢明に行うための良い議論があります。
ただし、常に最上位でリッスンし、すべてのデータを単純に渡すことを好みます。ストアの状態全体を単一のオブジェクトとして階層全体に渡すこともありますが、これを複数のストアに対して行います。したがって、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
店舗間で店舗間でデータを関連付けるロジックを配置する場合があることに注意してください。したがって、あなたのArticleStore
はwaitFor()
UserStore
であり、getArticles()
を介して提供するすべてのArticle
レコードに関連するユーザーを含めることができます。ビューでこれを行うことは、ビューレイヤーにロジックをプッシュするように聞こえますが、これは可能な限り避けるべきプラクティスです。
transferPropsTo()
を使用したいと思われるかもしれませんが、多くの人はこれを好んでいますが、読みやすさ、したがって保守性のためにすべてを明示的に保つことを好みます。
FWIW、私の理解では、David NolenはOmフレームワーク( ややFlux-compatible である)と同様のアプローチを取り、ルートノード上のデータの単一のエントリポイントを使用することです-Fluxの同等物はすべてのストアをリッスンする1つのコントローラービューのみを持つようにします。これは、shouldComponentUpdate()
と、===を使用して参照によって比較できる不変のデータ構造を使用することで効率的になります。不変のデータ構造については、Davidの mori またはFacebookの immutable-js をチェックアウトしてください。 Omについての私の限られた知識は、主に JavaScript MVCフレームワークの未来
私の解決策ははるかに簡単です。独自の状態を持つすべてのコンポーネントは、ストアと会話したり聞いたりすることができます。これらは非常にコントローラーのようなコンポーネントです。状態を維持せず、レンダリングだけを行うより深いネストされたコンポーネントは許可されません。彼らは、非常にビューのような純粋なレンダリングの小道具のみを受け取ります。
このように、すべてがステートフルコンポーネントからステートレスコンポーネントに流れます。ステートフルカウントを低く保つ。
あなたの場合、Articleはステートフルであり、したがってストアと対話し、UserLinkなどはレンダリングするだけなので、propとしてarticle.userを受け取ります。
2つの哲学で説明されている問題は、単一のページアプリケーションに共通しています。
このビデオで簡単に説明します: https://www.youtube.com/watch?v=IrgHurBjQbg and Relay( https://facebook.github.io/relay =)は、あなたが説明するトレードオフを克服するためにFacebookによって開発されました。
リレーのアプローチはveryデータ中心です。これは、「サーバーへの1つのクエリでこのビューの各コンポーネントに必要なデータだけを取得するにはどうすればよいですか?」という質問に対する答えです。また、Relayは、コンポーネントが複数のビューで使用されている場合、コード全体でほとんど結合しないようにします。
Relayがオプションではない場合、「すべてのエンティティコンポーネントが独自のデータを読み取る」ことは、あなたが説明する状況に対して、より良いアプローチのようです。 Fluxの誤解が店だと思います。ストアの概念は、モデルまたはオブジェクトのコレクションが保持される場所ではありません。ストアは、ビューがレンダリングされる前にアプリケーションがデータを置く一時的な場所です。それらが存在する本当の理由は、異なるストアに入るデータ全体の依存関係の問題を解決することです。
Fluxが指定していないのは、ストアがモデルの概念とオブジェクトのコレクション(バックボーンなど)にどのように関係するかです。その意味で、一部の人々は、ユーザーがブラウザを開いたままにする間、フラッシュしない特定のタイプのオブジェクトのコレクションを置く場所を実際にフラックスストアにしていますが、フラックスを理解すると、それはストアではありませんあるはずです。
解決策は、ビューをレンダリングするために必要なエンティティ(および潜在的にそれ以上)が格納され、更新された状態を維持する別のレイヤーを用意することです。このレイヤーがモデルとコレクションを抽象化する場合、サブコンポーネントが独自のデータを取得するために再度クエリを実行する必要がある場合、問題はありません。