【问题标题】:Issues with Redux - Adding & Removing Items From StateRedux 的问题 - 从状态中添加和删除项目
【发布时间】:2017-05-25 16:38:45
【问题描述】:

我正在处理一个购物车,我正试图解决我的应用程序的两个问题:

将商品添加到商店会覆盖商店中以前的商品:

初始状态:

const initialState = {
  items: {},
  showCart: false
};

添加到购物车减速器:

问题:这适用于将商品添加到购物车,但是当我在购物车中添加另一个商品时,它会覆盖前一个商品。为什么会这样/如何将项目保留在以前的状态?

let addToCartState = {...state,
  items: {
    [action.id]: {
      id: action.id,
        color: action.product_selection.color,
        size: action.product_selection.size,
        quantity: 1
       }
     },
  showCart: true
}
return state.merge(addToCartState);

从购物车减速器中删除所有内容:

问题:这似乎可行,但我似乎无法从状态图中获取数据。我似乎无法像在其他州一样调用“state.cart.items”(参见 mapStateToProps)。

let removeFromCartState = {...state,
  items: {
    ...state.items
  },
  showCart: true
}


function mapStateToProps(state) {
  console.log(state.cart);
  console.log("????");
  return { products: state.products, items: state.cart.items }
}

state.cart:

Map {size: 8, _root: ArrayMapNode, __ownerID: undefined, __hash: undefined, __altered: false}
  size: 8
  __altered: false
  __hash: undefined
  __ownerID: undefined
  _root: ArrayMapNode
  entries: Array(8)
    0: Array(2)
      0: "items"
      1: Map
        size: 0
   ...

^ 现在没有项目(大小:0,在上一个减速器之后是 1);我现在需要使用 fromJS 之类的东西来解析它还是不必这样做?

编辑 - combineReducers:

import {combineReducers} from 'redux';
import app from './appReducer';
import products from './productsReducer';
import cart from './cartReducer';
import user from './userReducer';

export default combineReducers({
  app: app,
  products: products,
  cart: cart,
  user: user
});

【问题讨论】:

  • 您是否设置了combineReducers,它的结构如何?对于“添加到购物车减速器”,您还必须复制嵌套对象。所以{...state, items: { ...state.items, /* etc.*/ } }
  • 我刚刚尝试复制嵌套对象,但奇怪的是我仍然得到同样的结果,一次只能将一种产品放入购物车。我确实有一个 combineReducers,就像:export default combineReducers({ app: app, products: products, cart: cart, user: user });
  • 如果您使用扩展运算符(...state...state.items),则不必将新状态与旧状态合并。这本质上是复制所有内容,并且当您将值更新为覆盖以前的对象中的键时。此外,更新您的代码以包含combineReducers(如果有)。这应该可以帮助我为您提供更好的答案。此外,如果代码位于单独的文件中,请将它们分开。一大段代码让它看起来像是在同一个文件中。
  • @KeithAlpichi 添加了我的 combineReducers,不确定这是否有帮助。这就是我对扩展运算符的想法,但无论出于何种原因,它实际上并没有保留先前状态的数据。

标签: javascript ecmascript-6 redux immutable.js


【解决方案1】:

问题的根源在于您将 Immutable.js 对象视为常规 JavaScript 对象,而不是使用旨在执行任务的内置 Immutable.js 功能。

问题:这适用于将商品添加到购物车,但是当我在购物车中添加另一个商品时,它会覆盖前一个商品。为什么会这样/如何将项目保留在以前的状态?

让我们看看你的代码:

let addToCartState = { ...state,
  items: { [action.id]: { /* ... */ } },
  showCart: true
};

扩展运算符 (...) 执行“浅”合并。你的代码在做什么,本质上是这样的:

let addToCartState = shallowCopy(state);
addToCartState.items = { [action.id]: { /* ... */ } };
addToCartState.showCart = true;

换句话说,它“覆盖了前一项”,因为您将items 属性替换为只有一项的新对象。一种解决方案是自己合并items

const addToCartState = { ...state,
  items: { ...state.items,
    [action.id]: { /* ... */ },
  },
  showCart: true,
};

...但是由于您使用的是 Immutable.js,因此您不应该这样做。您应该使用其内置的mergeDeep 方法:

function addToCart(prevState, action) {
  const addToCartState = {
    items: {
      [action.id]: {
        color: action.product_selection.color,
        // ...
      },
    },
    showCart: true,
  };
  return prevState.mergeDeep(addToCartState);
}

let state = Immutable.fromJS({ items: {} });
console.log('Original state:', state);

console.log('Add blue thing');
state = addToCart(state, {
  id: '123',
  product_selection: { color: 'blue' },
});

console.log('State is now:', state);

console.log('Add green thing');
state = addToCart(state, {
  id: '456',
  product_selection: { color: 'green' },
});

console.log('State is now:', state);
.as-console-wrapper{min-height:100%}
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.min.js"></script>

问题:这似乎可行,但我似乎无法从状态图中获取数据。我似乎无法像在其他州一样调用“state.cart.items”(参见 mapStateToProps)。

state 不是一个“普通”的 JavaScript 对象,它是一个 Immutable.Map。您不能像普通对象属性那样访问它的值。一种解决方案是使用toJS 将其转换为普通对象,然后像往常一样检索其属性(和子属性)。如果您的状态对象可能很大,另一种选择是使用 Immutable.js 的 getgetIn (用于“深度”属性)检索值。对于后者,如果它们也是不可变对象,则必须在单个值上使用 toJS。您可以在下面看到这两种方法。

function mapStateToProps(state) {
  const obj = state.toJS();
  return { products: obj.products, items: obj.cart.items };
}

// or...

function mapStateToPropsAlt(state) {
  return {
    products: state.get('products').toJS(),
    items: state.getIn(['cart', 'items']).toJS(),
  };
}

const state = Immutable.fromJS({
  products: [ '¯\\_(ツ)_/¯' ],
  cart: {
    items: {
      '123': { id: '123', color: 'blue', /* ... */ },
    },
  },
});

console.log('mapStateToProps(state) =>', mapStateToProps(state));
console.log('mapStateToPropsAlt(state) =>', mapStateToPropsAlt(state));
.as-console-wrapper{min-height:100%}
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.min.js"></script>

【讨论】:

  • 乔丹,你让我很开心。非常感谢你的惊人解释; mergeDeep 成功了,我终于有点理解 Immutable 了。谢谢!!!!
  • P.S.谁能告诉我我在这里做错了什么? @jordan-running 的 get 上面的代码有效,当在购物车减速器中时,我可以这样做:state.getIn(['items'])。但由于某种原因,我不能这样做state.delete(["items", action.id])
  • @albaba 这将有助于阅读文档。 deleteget 一样,采用单个值,例如state.delete('items')。要进行“深度”删除,您需要使用 deleteIn
  • 是的,文档现在让我失望了。 deleteIn 不适用于以下任何变体:state.deleteIn(['items', action.id])state.deleteIn([state.get('items')), action.id])state.deleteIn([state.get('items'), String(state.get('items', action.id))])(每 link)@jordan-running
  • @albaba 我无法重现这个;它works fine here。确保action.id 的值和 Immutable.Map 的键的值都是您所期望的。不要忘记 Immutable.Map 可以有任何类型的值作为键,而 JavaScript 属性名称总是强制转换为字符串。 let key = 123; Immutable.Map({ [key]: 'foo' }) 将产生一个带有字符串键'123' 的映射,而let key = 123; Immutable.Map().set(key, 'foo') 将产生一个带有数字键123 的映射。
猜你喜欢
  • 2016-10-13
  • 2018-05-02
  • 1970-01-01
  • 2019-12-22
  • 2020-12-15
  • 1970-01-01
  • 2019-03-25
  • 1970-01-01
  • 2017-11-07
相关资源
最近更新 更多