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
变量的新值重新运行函数组件,并触发一系列调用

调用堆栈将如下所示:

  • 使用效果运行
  • 更新数据调用
  • 设置状态调用
  • 组件使用新的状态数据重新渲染
  • 数据的新值
    触发useCallback
  • updateData
    已更改
  • 再次触发
    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]
    );