Redux - 如何从标准化状态计算购物车列表的成本

问题描述

我对 React/Redux 还很陌生。我有一个标准化的 redux 状态,如下所示:

users: {
  user1: {
    id: 'user1',name: 'Jim',carts: ['cart1','cart2']
  }
},carts: {
  cart1: {
    id: 'cart1',items: ['item1','item2']
  },cart2: {
    id: 'cart2',items: ['item3','item4']
  }
},items: {
  item1: {
    id: 'item1',price: 1
  },item2: {
    id: 'item2',price: 2
  },item3: {
    id: 'item3',price: 3
  },item4: {
    id: 'item4',price: 4
  }
}

我还创建了以下选择器:

item.selector.js:

export const selectAllItems = state => state.items

export const selectItems = itemIds => createSelector(
  [selectAllItems],items => Object.keys(items)
    .filter(itemId => itemIds.includes(itemId))
    .reduce((arr,itemId) => {
      arr.push(items[itemId])
      return arr
    },[])
)

export const totalItemsCost = itemIds => createSelector(
  [selectAllItems],items => Object.keys(items)
    .filter(itemId => itemIds.includes(itemId))
    .reduce((cost,itemId) => {
      cost += blocs[itemId].price
      return cost
    },0)
)

cart.selector.js:

export const selectAllCarts = state => state.carts

export const selectCart = cartId => createSelector(
  [selectAllCarts],carts => carts[cartId]
)

export const selectCarts = cartIds => createSelector(
  [selectAllCarts],options => Object.keys(carts)
    .filter(key => cartIds.includes(key))
    .reduce((arr,key) => {
      arr.push(options[key])
      return arr
    },[])
)

user.selector.js:

export const selectUsers = state => state.users

export const selectUser = userId => createSelector(
  [selectUsers],users => users[userId]
)

我希望在用户页面显示每个购物车的总成本并具有以下 React 组件:

class User extends React.Component {
  render () {
    return (
      {  this.props.carts.map(cart => (<displayComponent id={ cart.id } cost={ $TOTAL_CART_COST }/>))}
    )
  }
}

const mapStatetoProps = (state,ownProps) => ({
  user: selectUser(ownProps.match.params.userId)(state),carts: selectCarts(selectUser(ownProps.match.params.userId)(state).carts)(state)
})

export default connect(mapStatetoProps,null)(User)

我不确定实现这一目标的最佳方式。我尝试创建一个由减速器更新的 cartCost 字段,如下所示:

case CartActionTypes.ADD_ITEM_SUCCESS:
  return {
    ...state,[action.payload.cartId]: {
      ...state[action.payload.cartId],cartCost: totalItemsCost(addItemToCart(state[action.payload.cartId].items,action.payload.itemId))(state),items: addItemToCart(state[action.payload.cartId].items,action.payload.itemId)
    }
  }

使用:

export const addItemToCart = (items,newItem) => {
  const existingItem = items.find(item => item === newItem)

  if (existingItem) {
    return items
  } else {
    return [...items,newItem]
  }
}

但这给了我:

Uncaught (in promise) TypeError: Cannot convert undefined or null to object
    at Function.keys (<anonymous>)
    at item.selectors.js:18
    at index.js:70
    at index.js:30
    at index.js:84
    at index.js:30
    at cartReducer (cart.reducer.js:35)
    at combination (redux.js:459)
    at persistReducer.js:147
    at dispatch (redux.js:213)
    at redux-logger.js:1
    at index.js:11
    at dispatch (redux.js:638)
    at item.actions.js:30

欢迎所有建议,也许我没有采取正确的方法在减速器中创建新字段?谢谢

解决方法

这就是我的结局:

cart.selector.js:

export const selectAllCarts = state => state.carts

export const selectCart = cartId => createSelector(
  [selectAllCarts,selectAllItems],(carts,items) => generateCart(carts[cartId],items)
)

export const generateCart = (cart,allItems) => {
  return {
    ...cart,cartCost: calculateCartCost(allItems,cart.items)
  }
}
export const calculateCartCost = (allItems,cartItemIds) => {
  return Object.keys(allItems)
    .filter(itemId => cartItemIds.includes(itemId))
    .reduce((cost,itemId) => {
      cost += allItems[itemId].price
      return cost
    },0)
}

export const selectCarts = cartIds => createSelector(
  [selectAllCarts,items) => Object.keys(carts)
    .filter(key => cartIds.includes(key))
    .reduce((arr,key) => {
      arr.push(generateCart(carts[key],items))
      return arr
    },[])
)

现在给了我一个购物车列表,所有购物车都有我可以用来显示的购物车成本字段。很确定它不是最佳的,但我拥有的最好的 :)