useReducer钩子中reducer函数的问题



reducer函数工作得很好,除了以前添加到item数组属性的对象元素在添加新对象元素时被重写。例如,如果state.item包含{number: 1},我添加{number: 2},它变成[{number: 2},{number: 2}]而不是[{number: 1},{number: 2}]

减速器功能:

const reducer = (state, action) => {
if (action.type === "ADD") {
let newItem = state.item.concat(action.addItem);
console.log("action.addItem:", action.addItem);
console.log("state.item:", state.item);
return { item: newItem };
}
};

这个问题有解决办法吗?

谢谢。父组件:

import React from "react";
import { useReducer } from "react";
import { CreateContext } from "./CreateContext";
const initialState = {
item: [],
};
const reducer = (state, action) => {
if (action.type === "ADD") {
let newItem = state.item.concat(action.addItem);
console.log("action.addItem:", action.addItem);
console.log("state.item:", state.item);
return { item: newItem };
}
};
const AuthProvider = (props) => {
// define useReducer
const [state, dispatch] = useReducer(reducer, initialState);
// define handlers
const addItemHandler = (addItem) => {
console.log("addItemHandler");
dispatch({ type: "ADD", addItem: addItem });
};
const data = {
addItem: addItemHandler,
number: 0,
item: state.item,
};
return (
<CreateContext.Provider value={data}>
{props.children}
</CreateContext.Provider>
);
};
export default AuthProvider;

子组件:

import React, { useState, useContext } from "react";
import {
Button,
Card,
CardActionArea,
CardActions,
CardContent,
CardMedia,
makeStyles,
Typography,
Collapse,
TextField,
IconButton,
} from "@material-ui/core";
import clsx from "clsx";
import AddBoxIcon from "@material-ui/icons/AddBox";
import { Grid } from "@material-ui/core";
import { CreateContext } from "../Store/CreateContext";
const useStyles = makeStyles((theme) => ({
card: {
marginBottom: theme.spacing(5),
},
media: {
height: 250,
// smaller image for mobile
[theme.breakpoints.down("sm")]: {
height: 150,
},
},
priceDetail: {
marginLeft: theme.spacing(15),
},
numberTextField: {
width: 52,
},
addBtn: {
fontSize: 60,
},
}));
const data = {
id: null,
name: null,
price: null,
quantity: null,
};
// img and title from the feed component
const Food = ({ img, title, description, price, id }) => {
const classes = useStyles();
// expand the description
const [expanded, setExpanded] = React.useState(false);
const handleExpandClick = () => {
setExpanded(!expanded);
};
const newPrice = `RM${price.toFixed(2)} `;
////// process the form //////
//get quantity from TextField
const [quantity, setQuantity] = useState("");
const quantityHandler = (enteredQuantity) => {
console.log("enteredQuantity:", enteredQuantity);
setQuantity(enteredQuantity.target.value);
};
// use useContext
const AuthData = useContext(CreateContext);
const submitHandler = (e) => {
console.log("submit is pressed");
e.preventDefault();
data.id = id;
data.title = title;
console.log("data.id:", data.id);
data.price = price;
console.log("data.price:", data.price);
data.quantity = quantity;
console.log("quantity:", quantity);
AuthData.addItem(data);
console.log("AuthData:", AuthData.number);
};
return (
<Grid item xs={12} md={6}>
<form onSubmit={submitHandler}>
<Card className={classes.card} id={id}>
<CardActionArea>
<CardMedia className={classes.media} image={img} title="My Card" />
<CardContent>
<Typography gutterBottom variant="h5">
{title}
</Typography>
<Button
size="small"
color="primary"
className={clsx(classes.expand, {
[classes.expandOpen]: expanded,
})}
onClick={handleExpandClick}
aria-expanded={expanded}
aria-label="show more"
>
Learn More
</Button>
<CardActionArea>
<Collapse in={expanded} timeout="auto" unmountOnExit>
<CardContent>
<Typography paragraph>{description}</Typography>
</CardContent>
</Collapse>
</CardActionArea>
</CardContent>
</CardActionArea>
<CardActions>
{" "}
<Typography variant="h6" className={classes.priceDetail}>
{newPrice}
</Typography>
<Typography variant="h6" className={""}>
x
</Typography>
<TextField
id={id}
label="amount"
type="number"
// value={}
// onChange={}
className={classes.numberTextField}
label=""
variant="outlined"
min="1"
max="5"
step="1"
defaultValue="0"
size="small"
onChange={quantityHandler}
input={id}
// ref={quantity}
/>
<IconButton aria-label="" onClick={""} type="submit">
<AddBoxIcon
color="secondary"
className={classes.addBtn}
></AddBoxIcon>
</IconButton>
</CardActions>
</Card>
</form>
</Grid>
);
};
export default Food;

问题完全出在子组件上,与reducer无关。这就是你如何在你调度的动作中传递成为addItem有效载荷的数据。

我在下面复制了子组件(或者整个模块)的相关部分,因此您可以更清楚地看到问题:

const data = {
id: null,
name: null,
price: null,
quantity: null,
};
const Food = ({ img, title, description, price, id }) => {
// more code that isn't relevant here
const submitHandler = (e) => {
console.log("submit is pressed");
e.preventDefault();
data.id = id;
data.title = title;
console.log("data.id:", data.id);
data.price = price;
console.log("data.price:", data.price);
data.quantity = quantity;
console.log("quantity:", quantity);
AuthData.addItem(data);
console.log("AuthData:", AuthData.number);
};
// more code that isn't relevant here
}

传递给AuthData.addItemdata(最终传递给reducer)并不是每次都是一个新对象——它是一个单独的"全局"对象。(模块级)常量,每次使用它时只需对其进行修改。这就是导致您的状态发生变化的原因-因为最终在state.items数组中的每个对象都是对同一对象的引用,因此您所做的每个突变(在submitHandler中)最终都会改变每个副本!

你可以很容易地在子组件中修复这个问题,不是每次简单地改变相同的对象,而是重新创建一个新的对象。我看不出你这样做有什么理由,所以干脆停止吧!我将把它重写如下:

// note NO const data = ...
const Food = ({ img, title, description, price, id }) => {
// more code that isn't relevant here
const submitHandler = (e) => {
console.log("submit is pressed");
e.preventDefault();
const data = {};
data.id = id;
data.title = title;
console.log("data.id:", data.id);
data.price = price;
console.log("data.price:", data.price);
data.quantity = quantity;
console.log("quantity:", quantity);
AuthData.addItem(data);
console.log("AuthData:", AuthData.number);
};
// more code that isn't relevant here
}

这样做,每个对象在状态"item"数组将是唯一的,并且您可以在不影响旧数组的情况下调度新数组!

最新更新