我正在开发一个 React Native 应用程序,并面临一个问题,即在
useCallback
内多次异步函数调用后状态变量设置不正确。具体来说,当我在选项卡之间快速导航时,有时 tipPostList
会填充用于 freePostList
的数据,而 freePostList
保持正确。
CommunityPage.jsx
const [tipPostList, setTipPostList] = useState([]);
const [freePostList, setFreePostList] = useState([]);
useFocusEffect(
useCallback(() => {
const fetchPosts = async () => {
try {
const [responseTip, responseFree] = await Promise.all([
getPostsByType("TIP"),
getPostsByType("FREE"),
]);
setTipPostList(responseTip.data.slice(0, 3));
setFreePostList(responseFree.data.slice(0, 3));
} catch (error) {
Sentry.captureException(error);
}
};
fetchPosts();
}, []),
);
api.js
// .... axios creation and others...
export const getPostsByType = (type) => {
return api.get("/posts", {
params: {
type,
},
});
};
我检查了Postman中的请求,似乎后端返回了一致的结果。 这里似乎有什么问题?我没有编写任何用
tipPostList
设置 freePostList
的代码。
确保您的请求仅与当前屏幕相关。 使用标志来检查组件是否仍然已安装,如果检查为 false,则只需避免更新状态即可。
因此,仅当组件仍处于焦点状态时才会发生状态更新,从而防止在选项卡之间快速导航时出现错误更新。
const [tipPostList, setTipPostList] = useState([]);
const [freePostList, setFreePostList] = useState([]);
useFocusEffect(
useCallback(() => {
let isMounted = true; // Define a flag to track if the component is still mounted
const fetchPosts = async () => {
try {
const responseTip = await getPostsByType("TIP");
const responseFree = await getPostsByType("FREE");
if (isMounted) { // Only update state if the component is still mounted
if (responseTip?.data?.length > 0) setTipPostList(responseTip.data.slice(0, 3));
if (responseFree?.data?.length > 0) setFreePostList(responseFree.data.slice(0, 3));
}
} catch (error) {
console.log(error);
}
};
fetchPosts();
// Cleanup function to set isMounted to false when unmounting or losing focus
return () => {
isMounted = false;
};
}, []),
);
正如我们所知,异步调用将从主程序中发出,并且仅在完成时才返回。这是为了使调用非阻塞 - 不阻塞主程序。此设置的问题在于,如果多次调用同一异步函数,则每次调用完成后都会返回自己的结果。每次收到结果后,各个州都会得到更新。因此,无法保证状态将获得最新异步调用的结果。很容易不一致。
React 建议以这样的方式进行编码,即状态将获得关于最新异步调用的结果。这是通过忽略所有先前异步调用的结果来实现的。
React-Native 文档中引用的代码片段运行异步效果显示了相同的内容。
isActive 是用于此目的的变量。最重要的一点是清理函数 useEffect 返回。它实际上是一个闭包,可以在其封闭函数中访问完全相同的变量 isActive 。因此,React 运行时可以使用此清理闭包,这会将 isActive 更改为 false,以防当您在标签之间导航时组件在您的情况下卸载。请注意,每个异步调用都由单独的清理函数支持。并且每个异步调用的结果都会被忽略,除了最新的异步调用之外,卸载事件肯定尚未发生。
解决方案:
请为您的代码提供这样的保护,并使状态更新保持一致。
useFocusEffect(
React.useCallback(() => {
let isActive = true;
const fetchUser = async () => {
try {
const user = await API.fetch({ userId });
if (isActive) {
setUser(user);
}
} catch (e) {
// Handle error
}
};
fetchUser();
return () => {
isActive = false;
};
}, [userId])
);