如何在 redux 中不重复地更新数量?

How do I update the quantity without duplicate in redux?

我有添加和删除项目状态,它发送到购物车。我是我的购物车,当我添加时,它会重复。我是否需要为此增量创建一个减速器,以便它可以从 1 更新到 2 而不是复制它?

const INITIAL_STATE = []

export const addItem = createAction('ADD_ITEM')
export const increment = createAction('INCREMENT')

export const removeItem = createAction('REMOVE_ITEM')

export default createReducer(INITIAL_STATE, {
  [addItem.type]: (state, action) => [...state, action.payload],
  [removeItem.type]: (state, action) => state.filter(item => item._id !== action.payload._id)
})

这是我的产品:

const INITIAL_STATE = []

export const addProducts = createAction('ADD_PRODUCTS')
export const addProduct = createAction('ADD_PRODUCT')

export default createReducer(INITIAL_STATE, {
  [addProducts.type]: (state , action) => [...state, action.payload],
  [addProduct.type]: (state, action) => [...action.payload]
})

我的减速器:

export default configureStore({
  reducer: {
    products: productsReducer,
    cart: cartReducer
  }
})

您的减速器有必要验证项目 ID 是否存在。案例存在,更新项目计数。而不是创建一个计数等于 1 的新项目:

export default createReducer(INITIAL_STATE, {
  [addItem.type]: (state, action) => {
    const pos = state.map((i) => i._id).indexOf(action.payload._id);
    if (pos !== -1) {
      state[pos] = { ...action.payload, count: state[pos].count + 1 };
    } else {
      state.push({ ...action.payload, count: 1 });
    }
  },
  [removeItem.type]: (state, action) =>
    state.filter((item) => item._id !== action.payload._id),
});

数量

现在您的 cart 状态正在保存购物车中所有商品的数组。任何地方都没有quantity 属性。您可以做的一件事是在将项目添加到购物车时向项目对象添加 quantity: 1 属性。但是...

正常化状态

您实际上不需要保存 while item 对象,因为您已经在 products 中拥有该信息。您真正需要知道的是购物车中每件商品的 id 及其 quantity。最合乎逻辑的数据结构是字典对象,其中键是项目 ID,值是相应的数量。

因为你只需要 id 我建议你的动作创建者 addItemremoveItemincrement 应该只是 [=17= 的函数] 而不是整个 item。这意味着您可以像 dispatch(addItem(5)) 这样称呼它们,而您的 action.payload 就是 idaddProductaddProducts 操作仍然需要整个 item 对象。

一组产品就可以了,您可以随时调用 state.products.find(item => item._id === someId) 以通过其 ID 查找产品。但是带键的对象更好!您希望键是项目 ID,值是相应的对象。 Redux Toolkit 通过 createEntityAdapter 帮助程序内置了对这种结构的支持。

创建切片

尽管 createAction 定义您的操作没有任何问题,但不需要。您可以将 createActioncreateReducer 替换为结合了两者功能的 createSlice

这是编写产品的另一种方式:

import { createSlice, createEntityAdapter } from "@reduxjs/toolkit";

const productsAdapter = createEntityAdapter({
  selectId: (item) => item._id
});

const productsSlice = createSlice({
  // this becomes the prefix for the action names
  name: "products",
  // use the initial state from the adapter
  initialState: productsAdapter.getInitialState(),
  // your case reducers will create actions automatically
  reducers: {
    // you can just pass functions from the adapter
    addProduct: productsAdapter.addOne,
    addProducts: productsAdapter.addMany
    // you can also add delete and update actions easily
  }
});

export default productsSlice.reducer;
export const { addProduct, addProducts } = productsSlice.actions;

// the adapter also creates selectors
const productSelectors = productsAdapter.getSelectors(
  // you need to provide the location of the products relative to the root state
  (state) => state.products
);

// you can export the selectors individually
export const {
  selectById: selectProductById,
  selectAll: selectAllProducts
} = productSelectors;

你的购物车:

import {createSlice, createSelector} from "@reduxjs/toolkit";

const cartSlice = createSlice({
  name: 'cart',
  // an empty dictionary object
  initialState: {},
  reducers: {
    addItem: (state, action) => {
      const id = action.payload;
      // add to the state with a quantity of 1
      state[id] = 1;
      // you might want to see if if already exists before adding
    },
    removeItem: (state, action) => {
      const id = action.payload;
      // you can just use the delete keyword to remove it from the draft
      delete state[id];
    },
    increment: (state, action) => {
      const id = action.payload;
      // if you KNOW that the item is already in the state then you can do this
      state[id]++;
      // but it's safer to do this
      // state[id] = (state[id] || 0) + 1
    }
  }
})

export default cartSlice.reducer;
export const {addItem, removeItem, increment} = cartSlice.actions;

// you can select the data in any format
export const selectCartItems = createSelector(
  // only re-calculate when this value changes
  state => state.cart,
  // reformat into an an array of objects with propeties id and quantity
  (cart) => Object.entries(cart).map(([id, quantity]) => ({id, quantity}))
)

// select the quantity for a particular item by id
export const selectQuantityById = (state, id) => state.cart[id]

// you can combine the ids with the products, but 
// I actually recommend that you just return the ids and get the
// product data from a Product component like <Product id={5} quantity={2}/>
export const selectCartProducts = createSelector(
  // has two input selectors
  state => state.cart,
  state => state.products,
  // combine and reformat into an array of objects
  (cart, products) => Object.keys(cart).map(id => ({
    // all properties of the product
    ...products.entries[id],
    // added quantity property
    quantity: cart[id],
  }))
)