Vuex中如何实现状态归一化?

How to achieve state normalization in Vuex?

我正在开发一个也使用 Vuex 的应用程序,只是遇到了处理嵌套对象会很痛苦的情况,所以我尝试尽可能地规范化(展平)状态,如下例所示:

users: {
    1234: { ... },
    46473: { name: 'Tom', topics: [345, 3456] }
},
userList: [46473, 1234]

我的问题是:当您的 API 响应如下所示时,"best" 实现上述目标的方法是什么:

data: [
    {id: 'u_0001', name: 'John', coments: [{id: 'c_001', body: 'Lorem Ipsum'}, {...}],
    {id: 'u_0002', name: 'Jane', coments: [{id: 'c_002', body: 'Lorem Ipsum'}, {...}],
    {...}
] 

假设 commentsusers 的子模块:

选项 1:

// action on the user module
export const users = ({ commit, state }, users) => {
    commit(SET_USERS, users)
    commit('comments/SET_COMMENTS', users)
}


// mutation on the user module
[types.SET_USERS] (state, users) {
     state.users = users.reduce((obj, user) => {
         obj[user.id] = {
             id: user.id, 
             name: user.name,
             comments: user.comments.map(comment => comment.id)
         } 
         return obj
     }, {})
    state.userIds = users.map(user => user.id)
},


// mutation on the comments module
[types.SET_COMMENTS] (state, users) {
    let allComments = []
    users.forEach(user =>  {
        let comments = user.comments.reduce((obj, comment) => {
            obj[comment.id] = comment
            return obj
        }, {})
        allComments.push(comments)
    })

   state.comments = ...allComments
},

IMO 这个选项很好,因为你不必担心每次更改页面时都重置状态(SPA/Vue-Router),避免了由于某种原因 id: u_001 不再存在的情况,因为每次调用突变时都会覆盖状态,但是将 users array 传递给两个突变感觉很奇怪。

选项 2:

// action on the user module
export const users = ({ commit, state }, users) => {
    // Here you would have to reset the state first (I think)
    // commit(RESET)

    users.forEach(user => {
        commit(SET_USER, user)
        commit('comments/SET_COMMENTS', user.comments)
    })
}


// mutation on the user module
[types.SET_USER] (state, user) {
    state.users[user.id] = {
        id: user.id, 
        name: user.name,
        comments: user.comments.map(comment => comment.id)
    }  
    state.userIds.push(user.id)
},


// mutation on the comments module
[types.SET_COMMENTS] (state, comments) {
    comments.forEach(comment => {
        Vue.set(state.comments, comment.id, comment)
    })
    state.commentsIds.push(...comments.map(comment => comment.id)
},

在这种情况下,需要重置状态,否则每次离开并重新登录页面时都会有 repeated/old 值。这有点烦人,更容易出现错误或不一致的行为。

结论 你们如何处理此类情况和 advices/best 做法?非常感谢回答,因为我坚持这些事情。

此外,我正在尝试避免像 Vue ORM、normalizr 等 3r 方库,因为需求并不那么复杂。

谢谢,

PS: 代码是自己写的,没有测试,可能有错误,请看大局。

嗯,为了避免下面状态的意外复杂性,是在进行状态归一化时需要注意的要点。

官方Redux下提到docs

  1. Each type of data gets its own "table" in the state.
  2. Each "data table" should store the individual items in an object, with the IDs of the items as keys and the items themselves as the values.
  3. Any references to individual items should be done by storing the item's ID.
  4. Arrays of IDs should be used to indicate ordering.

现在用上面的例子,从数据中去除冗余。您可以使用每个 table 来表示每个信息,例如 userscomments 等等。

{
  'users': {
    byId : {
      "user1" : {
        username : "user1",
        name : "User 1",
      },
      "user2" : {
        username : "user2",
        name : "User 2",
      },
      ...
    },
    allIds : ["user1", "user2", ..]
  },
  'comments': {
    byId : {
      "comment1" : {
        id : "comment1",
        author : "user2",
        body: 'Lorem Ipsum'
      },
      "comment2" : {
        id : "comment2",
        author : "user3",
        body: 'Lorem Ipsum'
    },
    allIds : ["comment1", "comment2"]
  }
}

通过这样做,我们可以确保更多的组件连接起来并负责查找和维护自己的数据集,而不是每个组件都有一个大数据集并将数据传递给子组件。

更新的答案

由于数据已根据组件规范化,通过单个操作从父组件传递实体并作为规范化的一部分,可以获得以下好处。

  1. Faster data access, no more iterating over arrays or nested objects.
  2. Loose coupling between components.
  3. Each component has its own place in the store, hence there is a single point of truth.

希望对您有所帮助!