更新状态数组时反应 useReducer 错误
React useReducer bug while updating state array
有一段时间没接触React了,现在正在复习中。好吧,我遇到了错误并尝试调试它大约 2 小时,但找不到错误。嗯,程序的主要逻辑是这样的:
- 购物车对象有一个主要上下文。
- Main 属性 是我存储所有产品的购物车数组
- 如果我添加具有相同名称的产品(我现在不将它与 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);
其他建议
- 将商品添加到购物车时,首先检查购物车是否已包含该商品,如果是,浅复制购物车和匹配的商品并更新商品的
value
属性 which好像是数量。
- Cart/item 总数通常是根据现有状态计算得出的值。因此,这些被认为是派生状态,它们不属于状态,这些应该在渲染时计算。参见 Identify the minimal (but complete) representation of UI state。如有必要,可以将它们记忆在购物车上下文中。
有一段时间没接触React了,现在正在复习中。好吧,我遇到了错误并尝试调试它大约 2 小时,但找不到错误。嗯,程序的主要逻辑是这样的:
- 购物车对象有一个主要上下文。
- Main 属性 是我存储所有产品的购物车数组
- 如果我添加具有相同名称的产品(我现在不将它与 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
, andshouldComponentUpdate
methods- Class component static
getDerivedStateFromProps
method- Function component bodies
- State updater functions (the first argument to
setState
)- Functions passed to
useState
,useMemo
, oruseReducer
<-- 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);
其他建议
- 将商品添加到购物车时,首先检查购物车是否已包含该商品,如果是,浅复制购物车和匹配的商品并更新商品的
value
属性 which好像是数量。 - Cart/item 总数通常是根据现有状态计算得出的值。因此,这些被认为是派生状态,它们不属于状态,这些应该在渲染时计算。参见 Identify the minimal (but complete) representation of UI state。如有必要,可以将它们记忆在购物车上下文中。