根据您在链接中提供的简化示例,问题是在第一次渲染后无条件调用useEffect()
的实现内部。鉴于您的用例,您不需要这种行为,因为您是根据您列举的条件手动调用它的。useFetch()
fetchData()
除此之外,您通过companiesFetch()
直接调用功能组件加剧了问题,您不应该这样做,因为在渲染期间fetchData()
会同步调用setState()
并修改组件的状态。
您可以通过在第一次渲染后首先修改useFetch()
以删除无条件获取,然后将条件逻辑移动到useEffect()
组件内的回调来解决这些问题。这是我的实施方式useFetch()
:
const initialFetchState = { isLoading: false, isError: false };
// automatically merge update objects into state
const useLegacyState = initialState => useReducer(
(state, update) => ({ ...state, ...update }),
initialState
);
export const useFetch = config => {
const instance = useMemo(() => axios.create(config), [config]);
const [state, setState] = useLegacyState(initialFetchState);
const latestRef = useRef();
const fetch = useCallback(async (...args) => {
try {
// keep ref for most recent call to fetch()
const current = latestRef.current = Symbol();
setState({ isError: false, isLoading: true });
const { data } = await instance.get(...args).finally(() => {
// cancel if fetch() was called again before this settled
if (current !== latestRef.current) {
return Promise.reject(new Error('cancelled by later call'));
}
});
setState({ data });
} catch (error) {
if (current === latestRef.current) {
setState({ isError: true });
}
} finally {
if (current === latestRef.current) {
setState({ isLoading: false });
}
}
}, [instance]);
return [state, fetch];
};
这就是我如何在内部调用它useEffect()
以防止同步调用setState()
:
const [companies, companiesFetch] = useFetch({ baseURL: API_ROOT() });
const [newUsers, setNewUsers] = useState({ companies: [] });
useEffect(() => {
if (!companies.isLoading) {
if (appStore.currentAccessLevel === 99 && newUsers.companies.length === 0) {
console.log('About to call companiesFetch');
companiesFetch('acct_mgmt/companies');
} else if (companies.status === 200 && newUsers.companies !== companies.data.companies) {
setNewUsers({ companies: companies.data.companies });
}
}
}, [appStore.currentAccessLevel, companies, newUsers]);
在您的示例中,我不确定在什么范围内newUsers
声明,但newUsers.companies = ...
在useEffect()
回调中看到引起了一些担忧,因为这意味着您的功能组件不纯,这很容易导致细微的错误。