2016-06-29 1 views
1

Я пытаюсь создать следующее: форум, на котором можно создать сообщение, которое запускает ADD_POST, и сообщение создается и добавляется в массив объектов «posts». Каждый объект «post» инициализируется массивом «комментариев», который будет содержать тексты комментариев («commentTxt»), введенные внутри этого сообщения.Как правильно обрабатывать состояние в двойных вложенных массивах (ReactJS + Redux)?

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] 

Затем, когда пользователь вводит этот пост, есть комментарий раздел, в котором пользователь может ввести текст комментария и новый объект будет добавлен (с помощью «ADD_COMMENT») в массиве «posts.comments»

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] 
     } 
     }) 

и отобразите его. И каждый раз, когда добавляется новый комментарий, новый будет отображаться вместе с предыдущими объектами комментария в массиве. Хотел бы сделать что-то вроде следующего:

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

Я слышал мутирует состояние напрямую не рекомендуется, так что я был бы признателен за любые рекомендации или представление о том, как правильно это сделать.

ответ

1

Возможно, вы захотите нормализовать свои данные. Таким образом, вместо того, чтобы хранить структуру, как это:

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

Вы бы хранить их, как это:

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

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

Это больше боли в первый, но позволяет большую гибкость.

В качестве альтернативы, вы можете изменить свой ADD_COMMENT редуктор:

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


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

Примечание: В этом последнем решении, нет никаких мутаций. Не знаю, как это будет выполняться с множеством комментариев, но я бы не предварительно оптимизировал этот сценарий, если у вас нет веских оснований.

+0

В том месте, где { this.props.post.comments.map ((комментарий) => { return }) }, как бы вы изменили его на основе новой версии ADD_COMMENT и получили доступ к состоянию, возвращенному из редуктора ADD_COMMENT через mapStateToProps и mapDispatchToProps? –

+0

Я не думаю, что любой из ваших кодов должен был бы измениться, кроме редуктора, поскольку форма данных остается нетронутой. Или, по крайней мере, я намеревался не изменять форму данных из ожидаемого. –

+0

Я просто зациклился на том, как я должен вызвать состояние внутри mapStateToProps, чтобы иметь возможность делать такие, как «this.props.post.comments». Кроме того, где бы я мог нормализовать данные? Извините, но если вы не возражаете, не могли бы вы показать полный пример. Кажется, я не обволакиваю голову. –

1

Как заявил Кристофер Дэвис в своем ответе, вы должны нормализовать свое состояние.Допустим, мы имеем форму, как это:

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' 
     } 
    } 
} 

Хорошо, теперь давайте писать какие-то действия создателей, что создает действие, которое будет мутировать состояние:

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 
}) 

Тогда вам понадобятся два восстановителей, чтобы обрабатывать сообщений порезать и комментарии порезать:

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 
    } 
} 

Давайте также создадим несколько селекторов, чтобы забрать данные из состояния:

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)) 
}) 

Тогда ваши компоненты:

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> 
) 

И теперь, подключенные контейнеры:

// 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) 

Это будет работать, но если у вас есть много сообщений, вы ударите проблемы с производительностью компонент ConnectedPost, потому что mapStateToProps, который зависит от компонента собственных реквизитов

Таким образом, мы должны переписать так:

// 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) 
    }) 
) 

и вуаля! Я не тестировал этот пример, но я думаю, это поможет вам увидеть, в каком направлении вам нужно идти.