【问题标题】:In React/Redux, how to calculate a Total price for a shopping cart在 React/Redux 中,如何计算购物车的总价
【发布时间】:2018-08-01 06:31:00
【问题描述】:

我在 Google 和 SO 上搜索了解决方案,但仍然找不到答案。他们都停留在“将商品添加到购物车”或“增加/减少数量”上,但从不计算总计,这很烦人!

在我的应用程序中,有一个商品列表,用户可以在其中输入商品的数量,这会更新其价格。 我想知道的是,您如何使用 Redux 将购物车中所有商品的所有价格汇总为 Total 价格并将其显示在我的 React 应用程序中?

另外,如果你能指出任何好的购物车教程,而不仅仅是在购物车中列出产品,我会很高兴。

行动:

/**
 * @description UPDATE SINGLE ITEM PRICE WHEN USER ENTER QUANTITY
 *
 * @param{number} price
 * @param{number} quantity - enter by user from input
 */
export const updateItemPrice = (price, quantity) => dispatch => {
  const result = price * quantity;

  return dispatch({
    type: UPDATE_ITEM_PRICE,
    subtotal: result
  });
};

减速机:

const INITIAL_STATE = {
  total: 0,
  subtotal: 0
};

const productsReducer = (state = INITIAL_STATE, action) => {
  switch (action.type) {
    // Update single item price
    case Types.UPDATE_ITEM_PRICE:
      return {
        ...state,
        subtotal: action.subtotal,
        total: // --> Here is where I'm stuck!!
      };

    default:
      return state;
  }
};

【问题讨论】:

  • 商店不应该计算任何东西,这不是它的责任。而是通过操作的状态提供总数。
  • 想法是,每当您从购物车中添加或删除商品时,您都应该更新 totalPrice。在 initialState 中将 totalPrice 保持为零。在 reducer 中,每当您删除项目时,都会降低价格并与添加项目相同

标签: javascript reactjs redux react-redux


【解决方案1】:

编辑:状态/动作/reducers 的更完整示例。

你真的需要将总数存储在 redux 中吗?通常,您希望在 redux 中保持最小状态,并计算您可以在选择器中的任何派生数据。小计和总计肯定属于这一类(除非你有一个非常不寻常的设置你自己的价格设置或其他东西),所以不要将它们存储在商店中,你可以根据需要计算它们,例如作为mapStateToProps 的一部分函数(假设您使用的是react-redux)。

以下是您所在州的示例。它包括两个主要切片,一个用于项目目录,第二个专门用于卡片。

{
  itemDetails: {
    item01: { name: 'Item One', price: 9.95 },
    item02: { name: 'Item Two', price: 10 },
    item03: { name: 'Item not appearing in this example', price: 50 },
  },
  cart: {
    item01: 1,
    item02: 2,
  },
}

查看购物车切片,所有 reducer 需要做的就是管理购物车中的数量,您可以通过基本操作来完成。 action 和 reducer 可能看起来像这样(这些都没有经过测试,只是为了提供一种外观):

// Cart actions
const incrementQuantityInCart = (itemId) => ({
  type: 'incrementQuantityInCart',
  itemId,
})

const decrementQuantityInCart = (itemId) => ({
  type: 'decrementQuantityInCart',
  itemId,
})

const removeItemFromCart = (itemId) => ({
  type: 'removeItemFromCart',
  itemId,
})

// Cart reducer, would be combined with a separate reducer using redux's `combineReducers`
const cart = (state = {}, action) => {
  switch (action.type) {
    case 'incrementQuantityInCart':
      const currentQuantity = state[action.itemId] || 0
      return {
        ...state,
        [action.itemId]: currentQuantity + 1,
      }
    case 'decrementQuantityInCart':
      const currentQuantity = state[action.itemId] || 0
      return {
        ...state,
        [action.itemId]: Math.max(currentQuantity - 1, 0),
      }
    case 'removeItemFromCart':
      return {
        ...state,
        [action.itemId]: 0,
      }
    default:return state
  }
}

然后你可以有一个选择器,例如:

function getCartContents(state) {
  const itemsInCart = Object.keys(state.cart)
    .filter(itemId => state.cart[itemId] > 0)
    .map(itemId => {
      const quantity = state.cart[itemId]
      const itemDetail = state.itemDetails[itemId]

      return {
        name: itemDetail.name,
        price: itemDetail.price,
        quantity,
        subtotal: itemDetail.price * quantity,
      }
    })

  const total = itemsInCart.reduce((total, item) => total + item.subtotal)

  return { itemsInCart, total }
}

// example output based on the above state
// {
//   itemsInCart: [
//     {
//       name: 'Item One',
//       price: 9.95,
//       quantity: 1,
//       subtotal: 9.95,
//     },
//     {
//       name: 'Item Two',
//       price: 10,
//       quantity: 2,
//       subtotal: 20,
//     },
//   ],
//   total: 29.95,
// }

然后,您可以在 mapStateToProps 中或作为您想要的任何组件使用此函数,并且它可以访问其 props 中的这些数据,因此您可以根据需要使用。

【讨论】:

  • 感谢您的回复。是的,我在项目中使用react-redux,但我是redux 的初学者。此外,我试图发布一个更全面的问题,但是(现在至少两次......)但被告知因为他们说它太长了,所以它要么太长要么太短。怎么办:|。无论如何,我会尝试你的解决方案,看看它是否有效。另外我想我必须修改购物车对象的结构,感谢您向我展示。
  • 我上面包含的示例并不是一个理想的解决方案,它更旨在让您了解它是如何工作的。 (例如,在一个真实的应用程序中,您更有可能将项目列表放在您的状态的一个单独切片中,并且在您的购物车切片中只包含项目 ID 和数量。)这听起来可能有点像您可能需要稍微调整一下你对商店里的东西的想法,但正如我所说,你通常希望在那里保持最小状态,然后计算可以从中得出的任何东西。如果可以的话,将尝试使用更完整的 reducer/actions 进行更新。
【解决方案2】:

我认为您需要更改构造存储在购物车中的购物车对象的方式。应该是这样的

cart: [
  {
    key: /* UNIQUE KEY TO IDENTIFY ITEM IN*/
    price: /* Number */
    quantity: /* number */
    total: /*price*quantity*/
  },
  {
    key: /* UNIQUE KEY TO IDENTIFY ITEM IN*/
    price: /* Number */
    quantity: /* number */
    total: /*price*quantity*/
  },
]

使用上述购物车结构,您可以更新单个商品,添加任何商品或从购物车中删除任何商品,并且您在减速器中基本上可以遍历购物车数组并使用每个存在的总键计算总价格对象,然后更新存储中的总数。

我希望它有所帮助。谢谢

【讨论】:

    【解决方案3】:

    看起来你这样做的方式非常错误。您应该在该州保留一份购物车物品列表。在这种情况下,您将能够在您需要的任何时间、任何地方计算购物车总数,这在减速器中是不必要的。代码应该是这样的

    动作

    export const addItem = (item/*{id, price}*/, quantity) => dispatch => {
    
      return dispatch({
        type: ADD_ITEM,
        item,
        quantity
      });
    };
    
    export const removeItem = (item/*{id, price}*/, quantity) => dispatch => {
    
      return dispatch({
        type: REMOVE_ITEM,
        item,
        quantity
      });
    };
    

    减速器

    const INITIAL_STATE = {
      items: {},
      total: 0
    };
    
    const cartReducer = (state = INITIAL_STATE, action) => {
      switch (action.type) {
        // Update single item price
        case Types.ADD_ITEM:
          {
              const items = Object.extend(state.items);
              const { item, quantity } = action;
              if (items[item.id]) {
                items[item.id].quantity += quantity;
              } else {
                items[item.id] = {price: item.price, quantity};
              }
              const total = Object.values(items)
                .reduce((result, cartItem) => result + cartItem.price*cartItem.quantity, 0);
    
              return {
                ...state,
                items,
                total
              };
          }
    
        case Types.REMOVE_ITEM:
          {
              const items = Object.extend(state.items);
              const { item, quantity } = action;
              if (items[item.id]) {
                items[item.id].quantity -= quantity;
              } else {
                items[item.id] = {price: item.price, quantity};
              }
              if (items[item.id] <= 0) {
                delete items[item.id];
              }
              const total = Object.values(items)
                .reduce((result, cartItem) => result + cartItem.price*cartItem.quantity, 0);
    
              return {
                ...state,
                items,
                total
              };
          }
    
        default:
          return state;
      }
    };
    

    代码只是为了展示一个大致的思路,reducers 代码是复制/粘贴,可以提取公共部分。

    【讨论】:

      【解决方案4】:

      您可以使用原生 javascript reduce()。您将使用产品对象的数量和价格属性将 cartItems 降低为总价。

      如果您正在处理购物车,您应该拥有购物车减速器和具有“cartItems”数组属性的购物车状态。另一件事是每个产品对象都应该有数量和价格对象。

      const INITIAL_STATE = {
        //you might have more properties
        cartItems: []
      };
      

      所以在你想要显示总价的组件(通常是结帐组件)中写下:

      const mapStateToProps = state => {
        total: state.cart.cartItems.reduce(
          //reduce go through the array and cartItem is the each item in the array
          (accumulatedTotal, cartItem) =>
            accumulatedTotal + cartItem.price * cartItem.quantity,
          0 //0 is the start point of accumulatedTotal
        );
      };
      

      然后你可以将“total”作为道具传递给你的组件

      这是结帐页面的示例

      const CheckoutPage = ({ cartItems, total }) => (
        <div className="checkout-page">
          <div className="checkout-header">
            <div className="header-block">
              <span>Product</span>
            </div>
            <div className="header-block">
              <span>Description</span>
            </div>
            <div className="header-block">
              <span>Quantity</span>
            </div>
            <div className="header-block">
              <span>Quantity</span>
            </div>
            <div className="header-block">
              <span>Remove</span>
            </div>
          </div>
          {cartItems.map(cartItem => (
            <CheckoutItem key={cartItem.id} cartItem={cartItem} />
          ))}
          <div className="total">
            <span>TOTAL:${total}</span>
          </div>
        </div>
      );
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-12-09
        • 1970-01-01
        • 1970-01-01
        • 2022-01-03
        • 1970-01-01
        • 2012-07-27
        相关资源
        最近更新 更多