React使用useContext更新登录状态时出现未挂载组件状态更新警告如何解决
Can't perfom a React state update on an unamounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in useEffect cleanup function
这个警告的核心触发逻辑是:你在登录请求成功后先执行了setUser(true)更新全局Context状态,App.js监听到user变为true后,直接将包含Login组件的AuthStackScreen卸载,切换为登录后的BuyerDashboardStackScreens。但此时Login组件的请求回调里还有setName('')、setPassword('')、setPress(false)等针对Login内部状态的更新操作,对已经卸载的组件执行状态更新就会抛出这个警告。
优先选择改动最小的方案即可:
方案1:调整状态更新顺序(最简便)
把Login组件内部状态的更新逻辑放到全局状态更新之前执行,确保内部状态更新完成后再触发路由切换卸载组件,修改后Login.js的成功分支逻辑如下:
if (response.data.Success == true) { const token = response.data.token.toString(); const super_user_status = response.data.super_user_status.toString(); const isLoggedIn = "1" console.log('Logged In and set Storgae') await AsyncStorage.multiSet([['isLoggedIn',isLoggedIn],['token', token], ['super_user_status', super_user_status]]) // 先更新Login组件内部状态 setName('') setPassword('') setPress(false) // 最后更新全局状态,触发路由切换 setUser(true) }
同时记得给失败分支、catch分支里的内部状态更新也做对应顺序调整,所有内部状态更新都放在全局状态更新之前。
方案2:增加组件挂载标记
如果存在组件可能在请求返回前就被卸载的场景,可以加挂载标记判断组件状态,仅在组件未卸载时执行状态更新:
import { useRef, useEffect } from 'react' const Login = ({ navigation }) => { // 标记组件是否处于挂载状态 const isMounted = useRef(true) // 组件卸载时修改标记 useEffect(() => () => isMounted.current = false, []) const login = async () => { // 原有逻辑不变,所有状态更新前先判断挂载状态 await axios(...).then(async function (response) { if (response.data.Success == true) { // 原有存储逻辑不变 await AsyncStorage.multiSet(...) if(isMounted.current) { setUser(true) setName('') setPassword('') setPress(false) } } else if (response.data.Success == false) { if(isMounted.current) { setPress(false) setErrorMsg(response.data.Error) setName('') setPassword('') } } }).catch(function (response) { if(isMounted.current) { setErrorMsg('Server Error 404'); setPress(false) } }) } }
方案3:请求自动取消
用axios自带的CancelToken在组件卸载时直接取消未完成的请求,不会进入后续的回调逻辑:
import { CancelToken } from 'axios' import { useEffect } from 'react' const Login = ({ navigation }) => { let cancelRequest // 组件卸载时取消未完成的登录请求 useEffect(() => () => cancelRequest?.(), []) const login = async () => { // 原有逻辑不变 await axios({ method: 'POST', url: api + 'login/', data: login_Credentials, headers: { 'Content-Type': 'multipart/form-data' }, // 注入取消令牌 cancelToken: new CancelToken(c => cancelRequest = c) }).then(/* 原有回调逻辑不变 */) } }
内容的提问来源于stack exchange,提问作者Adil Ijaz

