2016-06-29 5 views
1

Ich versuche, das folgende zu erstellen: ein Forum, wo man einen Beitrag erstellen kann, der die ADD_POST auslöst, und der Beitrag erstellt wird, und dem 'posts'-Objektarray hinzugefügt. Jedes 'post'-Objekt wird mit einem' comments'-Array initialisiert, das Kommentartexte ('commentTxt') enthält, die innerhalb dieses Posts eingegeben werden.Wie behandelt man den Zustand in doppelt verschachtelten Arrays (ReactJS + Redux) richtig?

let postReducer = function(posts = [], action) { 
    switch (action.type) { 
    case 'ADD_POST': 
     return [{ 
     id: getId(posts), //just calls a function that provides an id that increments by 1 starting from 0 
     comments: [ 
      { 
      id: getId(posts), 
      commentTxt: '' 
      } 
     ] 
     }, ...posts] 

Dann, wenn der Benutzer diese Stelle eintritt, gibt es einen Kommentar-Abschnitt, wo der Benutzer einen Kommentartext und ein neues Objekt eingeben würde (via ‚ADD_COMMENT‘) mit dem ‚posts.comments‘ array

hinzugefügt werden
case 'ADD_COMMENT': 
     return posts.map(function(post){ 
    //find the right 'post' object in the 'posts' array to update the correct 'comments' array. 
     if(post.id === action.id){ 
    //update 'comments' object array of a 'post' object by adding a new object that contains 'commentTxt', and replaces the current 'comments' array 
      return post.comments = [{ 
      id: action.id, 
    //new object is made with text entered (action.commentTxt) and added to 'post.comments' array 
      commentTxt: action.commentTxt 
      }, ...post.comments] 
     } 
     }) 

und würde es anzeigen. Und jedes Mal, wenn ein neuer Kommentar hinzugefügt wird, wird ein neues Element zusammen mit den vorherigen Kommentardokumenten im Array gerendert.

 { 
     this.props.post.comments.map((comment) => { 
      return <Comment key={comment.id} comment={comment} actions={this.props.actions}/> 
     }) 
     } 

Ich hörte mutiert Zustand direkt nicht zu empfehlen ist, so würde ich keine Anleitung oder Einsicht zu schätzen wissen, wie man so richtig tun, um: Wäre so etwas wie das folgende tun wollen.

Antwort

1

Sie könnten Ihre Daten normalisieren. Anstatt also Ihre Struktur wie folgt zu speichern:

posts: [{ 
    title: 'Some post', 
    comments: [{ 
    text: 'Hey there' 
    }] 
}] 

Sie würden speichern sie wie folgt aus:

posts: [{ 
    id: 1, 
    title: 'Some post' 
}] 

comments: [{ 
    id: 4, 
    postId: 1, 
    text: 'Hey there' 
}] 

Es ist mehr Schmerz zuerst, aber ein hohes Maß an Flexibilität ermöglicht.

Alternativ können Sie Ihre ADD_COMMENT Minderer ändern:

return posts.map(function(post) { 
    if (post.id !== action.id) { 
    return post 
    } 


    return { 
    ...post, 
    comments: [ 
     ...post.comments, 
     { 
     id: action.id, 
     commentTxt: action.commentTxt 
     } 
    ] 
    } 
} 

Hinweis: In dieser letzten Lösung gibt es keine Mutationen. Ich weiß nicht, wie es mit Tonnen von Kommentaren funktionieren würde, aber ich würde nicht für dieses Szenario voroptimieren, wenn Sie keinen guten Grund haben.

+0

In diesem Fall { this.props.post.comments.map ((Kommentar) => { Rückgabe }) } wird platziert, wie würden Sie es basierend auf der neuen Revision von ADD_COMMENT entsprechend ändern und auf den Status zugreifen, der von 'ADD_COMMENT' Reducer über mapStateToProps und mapDispatchToProps zurückgegeben wird? –

+0

Ich glaube nicht, dass irgendetwas von Ihrem Code geändert werden müsste, außer dem Reducer, da die Form der Daten unberührt bleibt. Zumindest wollte ich die Form der Daten nicht so verändern, wie Sie es erwartet hatten. –

+0

Ich bin gerade fest, wie ich den Staat innerhalb der mapStateToProps aufrufen sollte, wie zum Beispiel 'this.props.post.comments'. Wo würde ich die Daten normalisieren? Tut mir leid, aber wenn es Ihnen nichts ausmacht, könnten Sie ein komplettes Beispiel zeigen. Ich kann meinen Kopf nicht umschließen. –

1

Wie von Christopher Davies in seiner Antwort angegeben, sollten Sie Ihren Zustand normalisieren.Lassen Sie uns sagen, dass wir eine Form wie dieses:

const exampleState = { 
    posts: { 
     '123': { 
      id: '123', 
      title: 'My first post', 
      comments: [] // an array of comments ids 
     }, 
     '456': { 
      id: '456', 
      title: 'My second post', 
      comments: [] // an array of comments ids 
     } 
    }, 
    comments: { 
     'abc': { 
      id: 'abc', 
      text: 'Lorem ipsum' 
     }, 
     'def': { 
      id: 'def', 
      text: 'Dolor sit' 
     }, 
     'ghi': { 
      id: 'ghi', 
      text: 'Amet conseguir' 
     } 
    } 
} 

Ok, jetzt lassen Sie uns einige Aktionen Schöpfer schreiben, die Aktion erstellt, die den Zustand mutiert:

const addPost = (post) => ({ 
    type: 'ADD_POST', 
    post 
}) 

const addComment = (postId, comment) => ({ // for the sake of example, let's say the "comment" object here is a comment object returned by some ajax request and having it's own id 
    type: 'ADD_COMMENT', 
    postId, 
    comment 
}) 

Dann werden Sie zwei Reduzierungen müssen handhaben die Beiträge in Scheiben schneiden, und die Kommentare in Scheiben schneiden:

const postsReducer = (posts = {}, action = {}) => { 
    switch(action.type) { 
     case 'ADD_POST': 
      const id = getId(posts) 
      return { 
       ...posts, 
       [id]: action.post 
      } 
     case 'ADD_COMMENT': 
      return { 
       ...posts.map(p => { 
        if (p.id == action.postId) { 
         return { 
          ...p, 
          comments: p.comments.concat([action.comment.id]) 
         } 
        } 
        return p 
       }) 
      } 
     default: 
      return state 
    } 
} 

const commentsReducer = (comments = {}, action = {}) => { 
    switch(action.type) { 
     case 'ADD_COMMENT': 
      return { 
       ...comments, 
       [action.comment.id]: action.comment 
      } 
     default: 
      return state 
    } 
} 

Lassen Sie sich auch einige Selektoren erstellen, um Daten aus dem Zustand zu holen:

const getPost = (state, id) => state.posts[id] 

const getCommentsForPost = (state, id) => ({ 
    const commentsIds = state.posts[id].comments 
    return state.comments.filter(c => commentsIds.includes(c.id)) 
}) 

Dann Ihre Komponenten:

const PostLists = (posts) => (
    <ul> 
     {posts.map(p) => <Post key={p} id={p} />} 
    </ul> 
) 

PostLists.propTypes = { 
    posts: React.PropTypes.arrayOf(React.PropTypes.string) //just an id of posts 
} 


const Post = ({id, title, comments}) => (
    <li> 
     {title} 
     {comments.map(c) => <Comment key={c.id} {...c}/>} 
    </li> 
) 

Post.propTypes = { 
    id: React.PropTypes.string, 
    comments: React.PropTypes.arrayOf(React.PropTypes.shape({ 
     id: React.PropTypes.string, 
     text: React.PropTypes.text 
    })) 
} 


const Comment = ({ id, text }) => (
    <p>{text}</p> 
) 

Und nun die angeschlossenen Behälter:

// the mapStateToProps if very simple here, we just extract posts ids from state 
const ConnectedPostLists = connect(
    (state) => ({ 
     posts: Objects.keys(state.posts) 
    }) 
)(PostLists) 


// The ConnectedPost could be written naively using the component props passed as the second argument of mapStateToProps : 
const ConnectedPost = connect(
    (state, { id }) => ({ 
     id, 
     title: getPost(state, id).title, 
     comments: getCommentsForPost(state, id) 
    }) 
)(Post) 

das, aber funktionieren wird, wenn Sie viele Beiträge haben, werden Sie Performance-Probleme treffen mit Die ConnectedPost Komponente, weil mapStateToProps, die von der Komponente eigenen Requisiten abhängt will trigger a re-render of the connected component for any change in the state

Also sollten wir es wie folgt umschreiben:

// Since the post id is never intended to change for this particular post, we can write the ConnectedPost like this : 
const ConnectedPost = connect(
    (_, { id}) => (state) => ({ 
     id, 
     title: getPost(state, id).title, 
     comments: getCommentsForPost(state, id) 
    }) 
) 

Und voilà! Ich habe dieses Beispiel nicht getestet, aber ich denke, es kann Ihnen helfen zu sehen, in welche Richtung Sie gehen müssen.