Redux: Normalizing Global State

2019-07-24 21:21发布

问题:

Assume the following situation:

1. Page has many posts
2. Post has many comments

I have the following reducers:

1. PagesReducer
2. PostsReducer
3. PostReducer
4. CommentsReducer

I have the following state right now:

pagesByTitle: {
  dorrisPage: {
    isFetching: false,
    data: {
      _id: "..."
      title: "dorrisPage",
    },
    posts: [
      {
        isFetching: false,
        data: {
          _id: "..",
          body: ".."
        },
        comments: [..]
       }
    ]
  }
}

The above structure looked okay initially, but I realized that I had to pass down the action for child states. For example, if I dispatched an action called

ADD_COMMENT

I would pass the action down to PagesReducer, PostsReducer, PostReducer, and CommentsReducer, and finally the CommentsReducer will handle that action. I think this is when I realized why normalizing states is recommended in Redux.

Can you help me with the following questions?

  1. Is my motivation for normalizing states correct in this context?
  2. What's the best way to normalize the example states?

回答1:

you should avoid nesting.

quote from redux docs:

In a more complex app, you’re going to want different entities to reference each other. We suggest that you keep your state as normalized as possible, without any nesting. Keep every entity in an object stored with an ID as a key, and use IDs to reference it from other entities, or lists. Think of the app’s state as a database. This approach is described in normalizr's documentation in detail.

For normalize state you can use normalizr

pages:{
    items:{
        1:{id: 1,title: "dorrisPage", posts:[33,57]}
        2:{id: 2, title: "anotherPage",posts:[57]}
    },
    isFetching: false,
    itemIds:[1,2,..]
},
posts:{
    items:{
        33:{id: 33,title: "Post33", comments:[1,2]}
        57:{id: 57, title: "Post57", comments:[]}
    },
    isFetching: false,
    itemIds:[33,57,..]
}
comments:{
    items:{
        1:{id: 1, user: "user1", text:"fds"}
        2:{id: 2, user: "user2", text:"fds2"}
    },
    isFetching: false,
    itemIds:[1,2,..]
}

"itemIds" is neccessary for items ordering

then reducers may look like this

export const posts = (state = initialState, action) => {
    switch (action.type) {

        case type.FETCH_POSTS_REQUEST:
        case type.FETCH_POSTS_FAILURE:
            return {...state, isFetching: action.isFetching};
        case type.FETCH_POSTS_SUCCESS:
        return {...state,
            isFetching: action.isFetching,
            items: action.entities.posts, itemsIds: action.result
        };

        case type.DELETE_POST:
        return {...state,
            items: omit(state.items, action.id),
            itemsIds: state.itemsIds.filter(id=>id !== action.id)
        };
        case type.UPDATE_POST:
        return {...state, items: {...state.items,
                [action.post.id]: {...state.items[action.post.id],...action.post}}};

        default:
        return state;
    }
}

much easier to query post by id:

const mapStateToProps = (state,ownProps) =({
    post:state.posts.items[ownProps.id]
})

for computing derived data from redux store, you can use Reselect for creating memoized, composable selector functions

video tutorial



标签: reactjs redux