简体   繁体   English

如何正确处理双嵌套数组(ReactJS + Redux)中的状态?

[英]How to properly handle state in double nested arrays (ReactJS + Redux)?

I am trying to build the following: a forum where one can create a post, which triggers the ADD_POST, and the post is created, and added to the 'posts' object array. 我正在尝试构建以下内容:一个论坛,可以在其中创建一个帖子,该帖子触发ADD_POST,并创建该帖子,并将其添加到“ posts”对象数组中。 The each 'post' object is initialized with a 'comments' array that will hold comment texts ('commentTxt') entered inside that post. 每个“帖子”对象都使用“评论”数组初始化,该数组将保存在该帖子内部输入的评论文本('commentTxt')。

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]

Then when the user enters that post, there is a comment section where the user can enter a comment text and a new object would be added (via 'ADD_COMMENT') to the 'posts.comments' array 然后,当用户输入该帖子时,会有一个注释部分,用户可以在其中输入注释文本,并将新对象(通过“ 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]
        }
      })

and would display it. 并显示出来。 And every time a new comment is added, a new would be rendered along with the previous comment objects in array. 每次添加新注释时,都会与数组中的先前注释对象一起呈现一个新注释。 Would want to do something like the following: 想要执行以下操作:

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

I heard mutating state directly is not recommended, so I would appreciate any guidance or insight on how to properly do so. 我听说不建议直接更改状态,因此,我希望您能就如何正确进行更改提供任何指导或见解。

You might consider normalizing your data. 您可以考虑将数据标准化。 So, instead of storing your structure like this: 因此,与其像这样存储您的结构:

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

You'd store them like this: 您可以这样存储它们:

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

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

It's more of a pain at first, but allows a lot of flexibility. 刚开始时比较痛苦,但是却具有很大的灵活性。

Alternatively, you could modify your ADD_COMMENT reducer: 另外,您可以修改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 
      }
    ]
  }
}

Note: In this last solution, there are no mutations. 注意:在最后一个解决方案中,没有任何突变。 Don't know how it would perform with tons of comments, but I wouldn't pre-optimize for that scenario unless you have good reason. 不知道如何处理大量评论,但是除非您有充分的理由,否则我不会针对这种情况进行预优化。

As stated by Christopher Davies in his answer, you should normalize your state. 正如克里斯托弗·戴维斯(Christopher Davies)在回答中所说,您应该使自己的状态正常化。 Let's say we have a shape like this : 假设我们有一个这样的形状:

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, now let's write some actions creators that creates action that will mutate the state : 好的,现在让我们来编写一些动作创建者,这些动作创建者创建会使状态发生变化的动作:

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

Then, you will need two reducers to handle the posts slice, and the comments slice : 然后,您将需要两个reducer来处理posts片段和comments片段:

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

Let's also create some selectors to pick up data from the state : 我们还创建一些选择器以从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))
})

Then, your components : 然后,您的组件:

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

And now, the connected containers : 现在,连接的容器:

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

That is going to work BUT, if you have many posts, you will hit performance issues with the ConnectedPost component because mapStateToProps that depends on component own props will trigger a re-render of the connected component for any change in the state 那样就可以了,但是,如果您有很多帖子,您将遇到ConnectedPost组件的性能问题,因为依赖于组件自己的道具的mapStateToProps 会触发状态状态的任何更改而重新渲染连接的组件

So we should rewrite it like this : 所以我们应该这样重写它:

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

And voilà ! 和瞧! I didn't test this example, but I think it can help you to see in which direction you need to go. 我没有测试此示例,但我认为它可以帮助您确定需要朝哪个方向前进。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM