我正在实现一个React购物车,并且我有一个useEffect钩子存在锁定循环的问题。
最初,我使用useEffect来获取购物车中的ID和产品数量,并将这些值存储在名为 shopCart 的对象的状态变量数组中:
// fetches the id's and quantities of all products in the user's shopping cart
// and assigns these values to the shopCart array
// Each value of the shopCart array should be an object like this: {id:random_id, quantity:number_of_products }]
useEffect(() => {
let auxCart = []; // auxiliary array that will receive the result of the fetch request
// postData: performs a POST request with {userId:userId} as the body of the request
postData('REQUEST1_URL', { userId: userId })
.then(data => {
for (let i = 0; i < data.shopCart.length; i++) {
auxCart[i] = {};
auxCart[i].id = data.shopCart[i].id; //this shopCart is not the same shopCart state variable. Just a value received from the database
auxCart[i].quantity = data.shopCart[i].quantity;
}
});
setShopCart(auxCart);
}, [])
此后,我通过添加带有产品信息的属性/键(例如产品标题,价格和图像)来修改 shopCart :
// Modifies shopCart, which should contain only id's and quantities at the moment,
// assigning details to each product such as price and title.
// Each value of shopCart should be an object like this:
// {id:random_id, quantity:number_of_products,
// title:product_name, price:product_price,
// oldPrice:old_product_price, image:array_of_images,
// type:type_of_product}
useEffect(() => {
console.log('useEffect2');
if (!isFirstRender) { //Ref hook variable used to test if the useEffect is in the first render or not
let aux = [...shopCart]; // creates a copy of shopCart
for (let i = 0; i < shopCart.length; i++) {
// fetches the details of each product
fetch('REQUEST2_URL').
then(res => res.json()).
then(res => {
aux[i].image = res[0].image;
aux[i].title = res[0].title;
aux[i].price = res[0].price;
aux[i].oldPrice = res[0].oldPrice;
aux[i].type = res[0].type;
});
}
setShopCart(aux);
}
}, [shopCart]);
问题是第二个useEffect多次打印'useEffect2',表明它处于锁定循环中。我该如何解决?我期待的是,由于获取的产品信息不会更改,因此第二个useEffect不会每次都重复,但确实会重复。我将 shopCart 用于其依赖数组中,以便等待以完成更新 shopCart 的第一个请求。
答案 0 :(得分:1)
之所以发生这种情况,是因为在您的第二个useEffect
中,您的依赖项数组中有shopCart
,并且在其中设置了shopCart
的函数中,因此它无限运行。
您可以在第一个useEffect
内完成所有用例。怎么样?您可以await
进行第一个fetch
响应,然后再响应第二个请求。 (如果需要,可以保留then()
,但IMO async...await
的方法要干净得多)
让它像这样:
useEffect(async () => {
let auxCart = []; // auxiliary array that will receive the result of the fetch request
// postData: performs a POST request with {userId:userId} as the body of the request
const data = await postData('REQUEST1_URL', { userId: userId })
for (let i = 0; i < data.shopCart.length; i++) {
auxCart[i] = {};
auxCart[i].id = data.shopCart[i].id; //this shopCart is not the same shopCart state variable. Just a value received from the database
auxCart[i].quantity = data.shopCart[i].quantity;
}
let aux = [...shopCart]; // creates a copy of shopCart
for (let i = 0; i < shopCart.length; i++) {
// fetches the details of each product
const res = await fetch('REQUEST2_URL').
const resData = await res.json();
aux[i].image = resData[0].image;
aux[i].title = resData[0].title;
aux[i].price = resData[0].price;
aux[i].oldPrice = resData[0].oldPrice;
aux[i].type = resData[0].type;
}
// do whatever you want with those objects
}, [])
为了使代码更简洁,您还可以摆脱那些aux
变量(似乎很像命令式编程语言),并开始使用map
,filter
等数组函数。根据您的需要。确实,在第一个for loop
中,您只是在可以直接使用该数组时将相同的值设置给另一个数组:
useEffect(async () => {
const shopCart = await postData('REQUEST1_URL', { userId: userId }).shopCart
let aux = [...shopCart]; // creates a copy of shopCart
for (let i = 0; i < shopCart.length; i++) {
// fetches the details of each product
const res = await fetch('REQUEST2_URL').
const resData = await res.json();
aux[i].image = resData[0].image;
aux[i].title = resData[0].title;
aux[i].price = resData[0].price;
aux[i].oldPrice = resData[0].oldPrice;
aux[i].type = resData[0].type;
}
// do whatever you want with those objects
}, [])
在第二个for..loop
中,您也可以使用map
,但这有点复杂,因为您应该在async
内使用map
函数,然后等待所有Promises
来解决使用Promise.all()
的问题,这只是一个全新的解决方案...留给您;)
答案 1 :(得分:1)
在代码中创建无限循环有两件事。通过将这两种东西组合,它创建了循环。
let aux = [...shopCart]; // creates a copy of shopCart
<=此操作会创建一个普通的 new 对象,因此始终会创建一个 new shopCart
,即使当内容可能相同
setShopCart(aux);
}
}, [shopCart]);
<=您正在将此新对象设置为shopCart
,并且由于React始终认为这是一个 new 对象,因此它总是重新运行useEffect
如何解决此问题?
我不确定为什么将您分成两个不同的useEffect
并在第二个shopCart
中添加useEffect
作为依赖项,但是根据您的描述,您始终可以将它们组合在一起到一个useEffect
中,然后使用 Promise 或 async / await 来确保仅在第一个API调用完成后才运行第二个API调用。有关更多详细信息,我认为Agustin发布了一个很好的示例。