奇怪的useEffect无限循环
通过我们用来处理 apiCalls 的钩子看到一些奇怪的事情,其中 api 响应结构的突然变化触发了我们应用程序中的无限请求循环。我已经将它缩小到重现错误的基本要求。
import React, {useEffect, useState, useMemo} from 'react';
const mockedApiCall = (resolvedValue, time) =>
new Promise((resolve, reject) => setTimeout(() => resolve(resolvedValue), time));
const useHook = (query) => {
const [error, setError] = useState(null);
const [data, setData] = useState([]);
useEffect(() => {
const asyncOperation = async () => {
const response = await mockedApiCall([], 1000);
// Testing something i know will fail:
const mappedData = response.data.map(a => a);
// if the above didn't fail we would update data
setData(mappedData);
};
// Clear old errors
setError(null);
// trigger operation
asyncOperation().catch(e => {
console.log('fail');
setError('Some error');
});
}, [query]);
return [data, error];
}
const HookIssue = props => {
const [data, error] = useHook({foo: 'bar'}); // Creates infinite loop
//const [data, error] = useHook('foo') // only fails a single time
// Alternative solution that also doesn't create infinite loop
// const query = useMemo(() => ({foo: 'bar'}), []);
// const [data, error] = useHook(query);
return null;
};
export default HookIssue;
有谁知道这里发生了什么?这个钩子应该只监听查询变量的变化,但在某些情况下会陷入无限循环。
编辑:为了给这个问题添加一些更奇怪的东西,如果我删除 useEffect 中的两个 setError 调用中的一个(或两个),它也会防止无限循环。
回答
你得到这个循环是因为你将一个对象传递给useHook(). 对象是引用类型——也就是说,它们不是由它们的值定义的。由于这个原因,以下语句的计算结果为 false:
{ foo: "bar" } === { foo: "bar" }
每次HookIssue渲染时,它都会创建一个{ foo: "bar" }. 这是useEffect()在依赖数组中传递给的,但是因为对象是引用类型 - 每个渲染上的每个对象都有不同的引用 - React 将始终“看到”该query值已更改。
这意味着每次 asyncOperation完成时,都会导致重新渲染HookIssue,这将导致触发另一个效果。
要解决此问题,您需要将值类型或字符串传递给依赖项数组,或者您需要query通过使用useMemo().