5

現在、react + redux プロジェクトに取り組んでいます。

また、normalizrを使用してデータ構造を処理し、再選択してアプリ コンポーネントの適切なデータを収集しています。

すべてうまくいっているようです。

葉のようなコンポーネントがストアからのデータを必要とする状況に陥っているためconnect()、コンポーネントにそれを実装する必要があります。

簡単な例として、アプリが、複数のユーザーがフィードバックを収集する本の編集システムであると想像してください。

Book
    Chapters
        Chapter
            Comments
        Comments
    Comments

アプリのさまざまなレベルで、ユーザーはコンテンツに貢献したり、コメントを提供したりできます。

チャプターをレンダリングしているとします。チャプターにはコンテンツ (および作成者) とコメント (それぞれに独自のコンテンツと作成者があります) があります。

現在、私はIDconnect()reselect基づいてチャプターのコンテンツを作成しています。

データベースは normalizr で正規化されているため、実際には章の基本的なコンテンツ フィールドと作成者のユーザー ID しか取得していません。

コメントをレンダリングするには、章にリンクされたコメントを再選択できる接続コンポーネントを使用し、各コメント コンポーネントを個別にレンダリングします。

繰り返しますが、データベースは normalizr で正規化されているため、実際には基本的なコンテンツとコメント作成者のユーザー ID しか取得できません。

ここで、作成者バッジのような単純なものをレンダリングするには、別の接続されたコンポーネントを使用して、所有しているユーザー ID からユーザーの詳細を取得する必要があります (章の作成者をレンダリングするときと、個々のコメントの作成者ごとの両方の場合)。

コンポーネントは次のような単純なものになります。

@connect(
    createSelector(
        (state) => state.entities.get('users'),
        (state,props) => props.id,
        (users,id) => ( { user:users.get(id)})
    )
)
class User extends Component {

    render() {
        const { user } = this.props

        if (!user)
            return null
        return <div className='user'>
                    <Avatar name={`${user.first_name} ${user.last_name}`} size={24} round={true}  />
                </div>

    }
}

User.propTypes = {
    id : PropTypes.string.isRequired
}

export default User

そして、それは一見うまく機能します。

私は逆のことを試み、より高いレベルでデータを非正規化して、たとえばチャプターデータがユーザー ID だけでなくユーザーデータを直接埋め込み、それをユーザーに直接渡すようにしましたが、それだけです。非常に複雑なセレクターを作成しているように見えました。私のデータは不変であるため、毎回オブジェクトを再作成するだけです。

それで、私の質問は、connect()アンチパターンのサインをレンダリングするためにストアに葉のようなコンポーネント(上記のユーザーのような)を持っていますか?

私は正しいことをしていますか、それともこれを間違った方法で見ていますか?

4

3 に答える 3

1

それは実際にはアンチパターンではないという@Kevin Heの答えに同意しますが、通常、データフローを追跡しやすくするより良いアプローチがあります。

リーフのようなコンポーネントを接続せずに目的を達成するために、セレクターを調整して、より完全なデータ セットを取得できます。たとえば、<Chapter/>コンテナ コンポーネントの場合、次を使用できます。

export const createChapterDataSelector = () => {
  const chapterCommentsSelector = createSelector(
    (state) => state.entities.get('comments'),
    (state, props) => props.id,
    (comments, chapterId) => comments.filter((comment) => comment.get('chapterID') === chapterId)
  )

  return createSelector(
    (state, props) => state.entities.getIn(['chapters', props.id]),
    (state) => state.entities.get('users'),
    chapterCommentsSelector,
    (chapter, users, chapterComments) => I.Map({
      title: chapter.get('title'),
      content: chapter.get('content')
      author: users.get(chapter.get('author')),
      comments: chapterComments.map((comment) => I.Map({
        content: comment.get('content')
        author: users.get(comment.get('author'))
      }))
    })
  )
}

この例では、特定のチャプター ID に特化したセレクターを返す関数を使用して、<Chapter />複数ある場合に各コンポーネントが独自のメモ化されたセレクターを取得できるようにします。<Chapter />(同じセレクターを共有する複数の異なるコンポーネントは、メモ化を破壊します)。また、別の再選択セレクターに分割chapterCommentsSelectorして、状態からデータを変換 (この場合はフィルター処理) するため、メモ化できるようにしました。

<Chapter />コンポーネントで を呼び出すことができます。これにより、必要なすべてのデータとそのすべての子孫createChapterDataSelector()を含む不変マップを提供するセレクターが提供されます。<Chapter />その後、通常どおり小道具を渡すことができます。

通常の React の方法で props を渡すことの 2 つの主な利点は、追跡可能なデータ フローとコンポーネントの再利用性です。<Comment />'content'、'authorName'、および 'authorAvatar' props を渡してレンダリングするコンポーネントは、理解しやすく、使いやすいものです。コメントを表示したいアプリのどこでも使用できます。コメントが書き込まれているときに、アプリがコメントのプレビューを表示するとします。「ダム」コンポーネントでは、これは簡単です。しかし、コンポーネントが Redux ストア内の一致するエンティティを必要とする場合、そのコメントがまだ書き込まれている場合、そのコメントがまだストアに存在しない可能性があるため、これは問題です。

connect()ただし、将来的には、コンポーネントをさらに先に進めることがより理にかなっている時期が来るかもしれません。これの 1 つの強力なケースは、最終目的地に到達するためだけに、それらを必要としない仲介コンポーネントを介して大量の小道具を渡していることがわかった場合です。

Redux ドキュメントから:

プレゼンテーション コンポーネントを分離するようにしてください。コンテナー コンポーネントは、都合のよいときに接続して作成します。同じ種類の子にデータを提供するために親コンポーネントのコードを複製しているように感じるときはいつでも、コンテナーを抽出します。一般に、親が「個人的な」データや子の行動について知りすぎていると感じたらすぐに、コンテナーを抽出します。一般に、理解可能なデータ フローとコンポーネントの責任範囲との間のバランスを見つけるようにしてください。

推奨されるアプローチは、接続されたコンテナー コンポーネントの数を減らして開始し、必要な場合にのみ、より多くのコンテナーを抽出することです。

于 2016-05-09T08:41:54.220 に答える