useReducer hook 中reducer 函数的问题

Problem with reducer function in useReducer hook

reducer 函数工作正常,除了先前添加到 item 数组 属性 的对象元素在添加新对象元素时会被覆盖。 例如,如果 state.item 包含 {number: 1} 并且我添加 {number: 2},它变成 [{number: 2},{number: 2}] 而不是 [{number: 1},{number: 2}]

reducer函数:

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
}

这样一来,state"item"数组中的每个对象都是唯一的,你可以派发新的而不影响旧的!