更新状态数组时反应 useReducer 错误

React useReducer bug while updating state array

有一段时间没接触React了,现在正在复习中。好吧,我遇到了错误并尝试调试它大约 2 小时,但找不到错误。嗯,程序的主要逻辑是这样的:

  1. 购物车对象有一个主要上下文。
  2. Main 属性 是我存储所有产品的购物车数组
  3. 如果我添加具有相同名称的产品(我现在不将它与 id 进行比较,因为它是一个需要修改的小项目)它应该只是将该产品的旧数量与新数量相加

好吧,我做了所有添加的逻辑,但是当我发现由于某种原因当我继续添加产品时,它会线性加倍,问题就开始了。如果你想检查完整的应用程序,我会在这里留下 github link。此外,我将只留下重要的组件。也许有一个我忘记考虑的小错误。我还删除了用于汇总相同产品数量的逻辑,因为现在不需要这样做。推入状态数组很重要。

Github: https://github.com/AndNijaz/practice-react-

//上下文

import React, { useEffect, useReducer, useState } from "react";

const CartContext = React.createContext({
  cart: [],
  totalAmount: 0,
  totalPrice: 0,
  addToCart: () => {},
  setTotalAmount: () => {},
  setTotalPrice: () => {},
});

const cartAction = (state, action) => {
  const foodObject = action.value;
  const arr = [];
  console.log(state.foodArr);
  if (action.type === "ADD_TO_CART") {
    arr.push(foodObject);
    state.foodArr = [...state.foodArr, ...arr];
    return { ...state };
  }
  return { ...state };
};

export const CartContextProvider = (props) => {
  const [cartState, setCartState] = useReducer(cartAction, {
    foodArr: [],
    totalAmount: 0,
    totalPrice: 0,
  });

  const addToCart = (foodObj) => {
    setCartState({ type: "ADD_TO_CART", value: foodObj });
  };

  return (
    <CartContext.Provider
      value={{
        cart: cartState.foodArr,
        totalAmount: cartState.totalAmount,
        totalPrice: cartState.totalAmount,
        addToCart: addToCart,
      }}
    >
      {props.children}
    </CartContext.Provider>
  );
};

export default CartContext;

//Food.js

import React, { useContext, useState, useRef, useEffect } from "react";
import CartContext from "../../context/cart-context";
import Button from "../ui/Button";
import style from "./Food.module.css";

const Food = (props) => {
  const ctx = useContext(CartContext);
  const foodObj = props.value;
  const amountInput = useRef();

  const onClickHandler = () => {
    const obj = {
      name: foodObj.name,
      description: foodObj.description,
      price: foodObj.price,
      value: +amountInput.current.value,
    };
    console.log(obj);
    ctx.addToCart(obj);
  };

  return (
    <div className={style["food"]}>
      <div className={style["food__info"]}>
        <p>{foodObj.name}</p>
        <p>{foodObj.description}</p>
        <p>{foodObj.price}$</p>
      </div>
      <div className={style["food__form"]}>
        <div className={style["food__form-row"]}>
          <p>Amount</p>
          <input type="number" min="0" ref={amountInput} />
        </div>
        <Button type="button" onClick={onClickHandler}>
          +Add
        </Button>
      </div>
    </div>
  );
};

export default Food;

//按钮 从“./Button.module.css”导入样式;

const Button = (props) => {
  return (
    <button
      type={props.type}
      className={style["button"]}
      onClick={props.onClick}
    >
      {props.children}
    </button>
  );
};

export default Button;

问题

React.StrictMode 组件暴露了无意的 side-effect。

Detecting Unexpected Side Effects

Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:

  • Class component constructor, render, and shouldComponentUpdate methods
  • Class component static getDerivedStateFromProps method
  • Function component bodies
  • State updater functions (the first argument to setState)
  • Functions passed to useState, useMemo, or useReducer <-- here

传递给 useReducer 的函数被双重调用。

const cartAction = (state, action) => {
  const foodObject = action.value;

  const arr = [];

  console.log(state.foodArr);
  if (action.type === "ADD_TO_CART") {
    arr.push(foodObject); // <-- mutates arr array, pushes duplicates!

    state.foodArr = [...state.foodArr, ...arr]; // <-- duplicates copied

    return { ...state };
  }
  return { ...state };
};

解决方案

Reducer 函数被认为是纯函数,获取当前状态和一个动作并计算下一个状态。从纯功能的意义上讲,相同的下一个状态应该来自相同的当前状态和操作。解决方案是根据当前状态只添加一次新的 foodObject 对象。

另请注意,默认“案例”只是 return 当前状态对象。在不更改任何数据的情况下浅复制状态将不必要地触发重新渲染。

我还建议将 reducer 函数重命名为 cartReducer,这样它的目的对于您的代码的未来读者来说更加清楚。

const cartReducer = (state, action) => {
  switch(action.type) {
    case "ADD_TO_CART":
      const foodObject = action.value;
      return {
        ...state, // shallow copy current state into new state object
        foodArr: [
          ...state.foodArr, // shallow copy current food array
          foodObject, // append new food object
        ],
      };

    default:
      return state;
  }
};

...

useReducer(cartReducer, initialState);

其他建议

  1. 将商品添加到购物车时,首先检查购物车是否已包含该商品,如果是,浅复制购物车和匹配的商品并更新商品的 value 属性 which好像是数量。
  2. Cart/item 总数通常是根据现有状态计算得出的值。因此,这些被认为是派生状态,它们不属于状态,这些应该在渲染时计算。参见 Identify the minimal (but complete) representation of UI state。如有必要,可以将它们记忆在购物车上下文中。