Reactjs useCallback的React useEffect依赖项始终触发渲染
我有一个谜。考虑以下按时间段获取数据的自定义响应钩子,并将结果存储在<代码>图 >:Reactjs useCallback的React useEffect依赖项始终触发渲染,reactjs,use-effect,usecallback,Reactjs,Use Effect,Usecallback,我有一个谜。考虑以下按时间段获取数据的自定义响应钩子,并将结果存储在图 >: export function useDataByPeriod(dateRanges: PeriodFilter[]) { const isMounted = useMountedState(); const [data, setData] = useState( new Map( dateRanges.map(dateRange => [
export function useDataByPeriod(dateRanges: PeriodFilter[]) {
const isMounted = useMountedState();
const [data, setData] = useState(
new Map(
dateRanges.map(dateRange => [
dateRange,
makeAsyncIsLoading({ isLoading: false }) as AsyncState<MyData[]>
])
)
);
const updateData = useCallback(
(period: PeriodFilter, asyncState: AsyncState<MyData[]>) => {
const isSafeToSetData = isMounted === undefined || (isMounted !== undefined && isMounted());
if (isSafeToSetData) {
setData(new Map(data.set(period, asyncState)));
}
},
[setData, data, isMounted]
);
useEffect(() => {
if (dateRanges.length === 0) {
return;
}
const loadData = () => {
const client = makeClient();
dateRanges.map(dateRange => {
updateData(dateRange, makeAsyncIsLoading({ isLoading: true }));
return client
.getData(dateRange.dateFrom, dateRange.dateTo)
.then(periodData => {
updateData(dateRange, makeAsyncData(periodData));
})
.catch(error => {
const errorString = `Problem fetching ${dateRange.displayPeriod} (${dateRange.dateFrom} - ${dateRange.dateTo})`;
console.error(errorString, error);
updateData(dateRange, makeAsyncError(errorString));
});
});
};
loadData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dateRanges /*, updateData - for some reason when included this triggers infinite renders */]);
return data;
}
导出函数UseDataTypeRiod(日期范围:PeriodFilter[]){
const isMounted=useMountedState();
const[data,setData]=useState(
新地图(
dateRanges.map(dateRange=>[
日期范围,
将AsyncIsLoading({isLoading:false})设置为AsyncState
])
)
);
const updateData=useCallback(
(句点:PeriodFilter,asyncState:asyncState)=>{
常量isSafeToSetData=isMounted==undefined | | |(isMounted!==undefined&&isMounted());
if(isSafeToSetData){
setData(新映射(data.set(period,asynchstate));
}
},
[setData,data,isMounted]
);
useffect(()=>{
如果(dateRanges.length==0){
返回;
}
常量加载数据=()=>{
const client=makeClient();
dateRanges.map(dateRange=>{
updateData(日期范围,makeAsyncIsLoading({isLoading:true}));
返回客户端
.getData(dateRange.dateFrom、dateRange.dateTo)
。然后(周期数据=>{
updateData(日期范围,makeAsyncData(periodData));
})
.catch(错误=>{
const errorString=`获取${dateRange.displayPeriod}(${dateRange.dateFrom}-${dateRange.dateTo})时出现问题';
console.error(errorString,error);
updateData(日期范围,makeAsyncError(errorString));
});
});
};
loadData();
//eslint禁用下一行react HOOK/deps
},[dateRanges/*,updateData-出于某种原因,如果包含此选项,将触发无限渲染*/];
返回数据;
}
当updateData
作为依赖项添加时,useffect
会反复触发。如果我将其作为依赖项排除,那么一切都会按预期工作/行为,但eslint
抱怨我违反了react hooks/deps
鉴于
updateData
已经useCallback
-ed,我不明白为什么它会反复触发渲染。有人能解释一下吗?这是我根据@jure上面的评论得出的结论:
我认为问题在于useCallback的依赖数组中包含了“data”变量。每次设置数据时,都会更改触发useCallback以提供新的updateData并触发useEffect的数据变量。尝试在不依赖数据变量的情况下实现updateData。您可以执行类似setData(d=>newmap(d.set(period,asyncState))的操作,以避免将“data”变量传递给useCallback 我按照建议的方式调整了代码,效果很好。谢谢
export function useDataByPeriod(dateRanges: PeriodFilter[]) {
const isMounted = useMountedState();
const [data, setData] = useState(
new Map(
dateRanges.map(dateRange => [
dateRange,
makeAsyncIsLoading({ isLoading: false }) as AsyncState<MyData[]>
])
)
);
const updateData = useCallback(
(period: PeriodFilter, asyncState: AsyncState<MyData[]>) => {
const isSafeToSetData = isMounted === undefined || (isMounted !== undefined && isMounted());
if (isSafeToSetData) {
setData(existingData => new Map(existingData.set(period, asyncState)));
}
},
[setData, isMounted]
);
useEffect(() => {
if (dateRanges.length === 0) {
return;
}
const loadData = () => {
const client = makeClient();
dateRanges.map(dateRange => {
updateData(dateRange, makeAsyncIsLoading({ isLoading: true }));
return client
.getData(dateRange.dateFrom, dateRange.dateTo)
.then(traffic => {
updateData(dateRange, makeAsyncData(traffic));
})
.catch(error => {
const errorString = `Problem fetching ${dateRange.displayPeriod} (${dateRange.dateFrom} - ${dateRange.dateTo})`;
console.error(errorString, error);
updateData(dateRange, makeAsyncError(errorString));
});
});
};
loadData();
}, [dateRanges , updateData]);
return data;
}
导出函数UseDataTypeRiod(日期范围:PeriodFilter[]){
const isMounted=useMountedState();
const[data,setData]=useState(
新地图(
dateRanges.map(dateRange=>[
日期范围,
将AsyncIsLoading({isLoading:false})设置为AsyncState
])
)
);
const updateData=useCallback(
(句点:PeriodFilter,asyncState:asyncState)=>{
常量isSafeToSetData=isMounted==undefined | | |(isMounted!==undefined&&isMounted());
if(isSafeToSetData){
setData(existingData=>newmap(existingData.set(period,asynchstate));
}
},
[设置数据,已安装]
);
useffect(()=>{
如果(dateRanges.length==0){
返回;
}
常量加载数据=()=>{
const client=makeClient();
dateRanges.map(dateRange=>{
updateData(日期范围,makeAsyncIsLoading({isLoading:true}));
返回客户端
.getData(dateRange.dateFrom、dateRange.dateTo)
。然后(流量=>{
updateData(日期范围,makeAsyncData(流量));
})
.catch(错误=>{
const errorString=`获取${dateRange.displayPeriod}(${dateRange.dateFrom}-${dateRange.dateTo})时出现问题';
console.error(errorString,error);
updateData(日期范围,makeAsyncError(errorString));
});
});
};
loadData();
},[dateRanges,updateData]);
返回数据;
}
问题在于组合使用的useCallback/useffect
。必须小心useCallback
和useffect
中的依赖项数组,因为useCallback
依赖项数组中的更改将触发useffect
运行
“data”
变量在useCallback
依赖项数组中使用,当调用setData时,react将使用data
变量的新值重新运行函数组件,并触发一系列调用
调用堆栈将如下所示:
数据的新值
触发useCallbackupdateData
已更改useffect
useCallback
依赖项数组中删除“data”
变量。我发现,尽可能不在依赖项数组中包含组件状态是一种很好的做法
如果需要从useffect
或useCallback
更改组件状态,且新状态为functi
const updateData = useCallback(
(period: PeriodFilter, asyncState: AsyncState<MyData[]>) => {
const isSafeToSetData = isMounted === undefined || (isMounted !== undefined && isMounted());
if (isSafeToSetData) {
setData(existingData => new Map(existingData.set(period, asyncState)));
}
},
[setData, isMounted]
);