typescript React -函数中的setState执行X次-已更新

uyto3xhc  于 2023-02-10  发布在  TypeScript
关注(0)|答案(1)|浏览(151)

快速概览:我有一个包含meals的上下文。该meals用于 * MealDetails-Component *。在此组件中,有一个列表,其中填充了一定数量的 * Food-Components *。如果您单击Food组件中的X,则会将其删除,否则您将转至FoodDetailspage
然后,当我按下按钮时,会执行名为 * deleteFood * 的函数,该函数将meals状态设置为新状态,但不包含该食物。问题是,在此组件中,函数中的setState不是调用一次,而是调用两次。我尝试在其他2个组件中使用该函数,其中一次仅执行一次,另一次执行4次。

    • 更新**

我的addFood函数有一个非常相似的问题。但是这个函数在另一个组件中被调用。它在2个不同的上下文中将Food添加到2个不同的状态,并且在这两个上下文中,添加的值都是双倍的。我可以"发现"的一件事是,我浏览器中的控制台第二次打印了我控制台在setState函数中记录的某个值,但不是通过MealsContext。而是通过react_devtools_backend. js。这只发生在我有错误的那两个函数上。

    • 更新2我在MealsDetails组件中显示食物组件,该组件包含来自MealsContext的meals**。这是问题所在吗?

MealsDetails.tsx

const MealDetails = () => {
  const navigate = useNavigate();

  let { id } = useParams();
  const { meals } = useMeals();

  const style = { "--percentage": "75%" } as React.CSSProperties;

  return (
    <div className="MealDetails">
      <header className="BarcodeScannerHeader">
        <ArrowBackIcon
          onClick={() => {
            navigate("/");
          }}
          sx={{ fontSize: 35 }}
        ></ArrowBackIcon>
        <div className="HeaderText">{meals.meals[Number(id)].name}</div>
      </header>
      <div className="MealDetailsContent">
              <span className="label">{meals.meals[Number(id)].calories} ate</span>
        <hr className="SolidMealDetails"></hr>
        {meals.meals[Number(id)] != null
          ? meals.meals[Number(id)].food.map((food: any, index: number) => <Food key={index} food={food}/>)
          : "Not loading"}
      </div>
      <Link className="Link" to={`/AddFood/${id!}`}>
        <div className="MealDetailsAddFoodButton">
          <img className="SVG" id="SVG" src={SVG} alt="+" />
        </div>
      </Link>
    </div>
  );
};

MealsContext.tsx

let initialState: MealsType = {
  calories: 0,
  carbs: 0,
  meals: [
    {
      calories: 0,
      carbs: 0,
      food: []
    }
  ],
};

const MealsProvider = ({ children }: any) => {
  const [meals, setMeals] = useState<MealsType>(initialState);

const addFood = async (
    id: number,
    addedFood: FoodType,
    selectedLocation: string,
    date: Date
  ) => {
    
    let res = await fetch("/meals/addFood", {
      method: "PATCH",
      credentials: "same-origin",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        mealId: id,
        addFood: addedFood,
        selectedLocation: selectedLocation,
        date: date,
      }),
    });

    const data = await res.json();

    if (res.status === 200) {
      console.log("func")
      setMeals((prevMeals) => {
        console.log("state");
        const updatedMeals = prevMeals;
        let foodExists = false;

        updatedMeals.calories += addedFood.kcal;
        updatedMeals.carbs += addedFood.carbs;
  
        updatedMeals.meals[id].calories += addedFood.kcal;
        updatedMeals.meals[id].carbs += addedFood.carbs;
  
        updatedMeals.meals[id].food.forEach((food : FoodType) => {
          if(food.code === addedFood.code){
            food.kcal += addedFood.kcal;
            food.carbs += addedFood.carbs;
            foodExists = true;
          }
        })

        if(!foodExists){
          updatedMeals.meals[id].food.push(addedFood);
        }
  
        return {...prevMeals,updatedMeals};
      });

      setUser(prevUser => {
        const updatedStorage = prevUser.storage;
  
        updatedStorage.map((storage : any) => {
          if(storage.location === selectedLocation){
            storage.storedFood.map((storedFood : any) => {
              if(storedFood.code === addedFood.code){
                storedFood.weight -= addedFood.weight;
              }
            })
          }
        })
  
        return {...prevUser, updatedStorage};
      })
      console.log(data);
    } else {
      console.log(data);
    }
    
  };

const deleteFood = (id: number, deletedFood: FoodType) => {
    setMeals((prevMeals) => {

      const updatedMeals = prevMeals;

      updatedMeals.calories -= deletedFood.kcal;
      updatedMeals.carbs -= deletedFood.carbs;

      updatedMeals.meals[id].calories -= deletedFood.kcal;
      updatedMeals.meals[id].carbs -= deletedFood.carbs;

      for(let i = 0; i < updatedMeals.meals[id].food.length; i++){
        if(updatedMeals.meals[id].food[i].code === deletedFood.code){
          updatedMeals.meals[id].food.splice(i, 1);
        }
      }

      return {...prevMeals,updatedMeals};
    });
  };
 return (
    <MealsContext.Provider
      value={{
        meals,
        deleteFood
      }}
    >
      {children}
    </MealsContext.Provider>
  );
};

const useMeals = () => useContext(MealsContext);

export { MealsProvider, useMeals };
};

Food.tsx

const Food = ({ food }: any) => {
  const location = useLocation();

  const {deleteFood} = useMeals();  

  return (
    <Link className="FoodContent Link" to={`/FoodDetails/`}  state= {{ food: food , location: location.pathname}} >
      <div className="Food">
        <div className="FoodDetail">
          <div className="FoodNameFoodDelete">
            <div className="FoodName">{food.name}</div>
            <img className="FoodDelete SVG" onClick={(e : any) => {e.preventDefault(); deleteFood(parseInt(location.pathname[location.pathname.length - 1]), food)}} src={SVG}></img>
          </div>
          <div className="FoodGram-FoodKcal">
            <div className="FoodBrand-FoodGram">
              <div className="FoodBrand">{food.brand + ","}&nbsp;</div>
              <div className="FoodGram">
                {food.weight ? food.weight + " g" : 100 + " g"}{" "}
              </div>
            </div>
            <div className="FoodKcal">{food.kcal} kcal</div>
          </div>
        </div>
        <div className="FoodNutritions">
          <div className="FoodNutrition">
            <div className="FoodNutritionGrams">{Math.round(food.carbs*10)/10} g</div>
            <div className="FoodNutritionText">Carbs</div>
          </div>
          <div className="FoodNutrition">
            <div className="FoodNutritionGrams">{Math.round(food.protein*10)/10} g</div>
            <div className="FoodNutritionText">Protein</div>
          </div>
          <div className="FoodNutrition">
            <div className="FoodNutritionGrams">{Math.round(food.fat*10)/10} g</div>
            <div className="FoodNutritionText">Fat</div>
          </div>
        </div>
      </div>
    </Link>
  );
};
qnakjoqk

qnakjoqk1#

主要问题是addFood()deleteFood()函数更改了状态,并且 * 此状态与上下文提供程序位于同一组件中 *,上下文提供程序为所有子组件设置状态。请记住,当您setState时,将导致该状态组件及其子组件重新呈现。

更好的解决方案是将状态设置逻辑与上下文提供程序分离。

MealsContext.ts之前:

let initialState = {};

const MealsProvider = ({ children }) => {
  const [meals, setMeals] = useState(initialState);

  const addFood = async () => {
    let res = await fetch(...);
    const data = await res.json();
    setMeals(); // <--- re-renders MealContext.Provider below and all sub-components
  };

 return (
    <MealsContext.Provider value={{ meals, addFood }}>
      {children}
    </MealsContext.Provider>
  );
};

const useMeals = () => useContext(MealsContext);

export { MealsProvider, useMeals };

MealsContext.ts之后:

let initialState = {};

const MealsProvider = ({ children }) => {
  const [meals, setMeals] = useState(initialState);

  return (
    <MealsContext.Provider value={{ meals, setMeals }}>
      {children}
    </MealsContext.Provider>
  );
};

const useMeals = () => useContext(MealsContext);

export { MealsProvider, useMeals };

useMethods.ts新的异步方法挂接:

import { useMeals } from "./MealsContext";

const useAsyncMethods = () => {
  const { setMeals } = useMeals();
  
  const addFood = async () => {
    let res = await fetch(...);
    const data = await res.json();
    setMeals(); // <--- re-renders only consumers of context, not the provider itself
  };
  
  return {
    addFood
  };
};

export default useAsyncMethods;

注意这里的主要变化只是从你的Provider中移除async / state,并返回上下文自身的setState(setMeals)方法。现在Provider被设置一次,当状态从async方法设置状态改变时,不会被重新挂载。现在只有钩子和它的消费者会重新呈现。
你需要更新现有的使用addFood()deleteFood()的文件,从新创建的钩子导入这些方法,而不是直接从上下文导入:

因此,与此相反

import useMeals from '.....';
...
const {addFood} = useMeals();

你会用钩子

import useMethods from './useMethods';
...
const {addFood} = useMethods();

上下文和状态可能很难一起处理,但是如果您尝试分离关注点并将组件分解为更小的部分,则会更容易理解问题是什么!

相关问题