Can't perform a React state update报错如何通过useEffect修复?
Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
这个警告的核心原因是:你的组件已经被用户关闭/路由跳转卸载了,但之前在useEffect中发起的getAllEnterprises接口请求还在运行,请求完成后的then/catch/finally回调依然执行了setEmpresas、setLoading等状态更新操作。React不允许对已卸载的组件更新状态,该操作虽然不会直接导致程序崩溃,但会造成内存泄漏——因为回调持有组件的引用,导致组件占用的内存无法被垃圾回收机制正常释放。
推荐优先使用标准的AbortController方案,该方案可以主动终止未完成的请求,不仅能解决警告,还能节省不必要的带宽开销:
export function SelecionarEmpresa({ closeModalSelectCompany }: Props) { const { token } = useAuth(); const { navigate } = useNavigation(); const [loading, setLoading] = useState(true); const [empresas, setEmpresas] = useState([]); useEffect(() => { // 创建终止控制器实例 const controller = new AbortController(); const signal = controller.signal; getAllEnterprises(token!, '7795297', signal) // 给请求传入signal参数 .then((res: any) => { // 请求成功且未被终止时再更新状态 if (!signal.aborted) { setEmpresas(res); } }) .catch((error: any) => { // 忽略请求主动终止的报错 if (error.name !== 'AbortError') { alert("Erro ao buscar empresas" + error); } }) .finally(() => { if (!signal.aborted) { setLoading(false); } }); // 组件卸载时的清理函数,主动终止请求 return () => controller.abort(); }, []); // 其余渲染代码保持不变即可 return ( <S.Container> <S.Gradient colors={['#8F012A', '#E67521']}> <S.Image source={require('../../images/fundobnb.png')} /> <S.SafeArea> { loading && ( <S.ContainerLoading> <ActivityIndicator size="small" color="#FFF" /> <S.Description>Carregando empresas...</S.Description> </S.ContainerLoading> ) } {!loading && !empresas?.length && ( <S.Content> <S.SubContent> <CustomIcon name="ops" size={normalize(40)} color="#FFA439" /> <S.TitleError>Ops!</S.TitleError> </S.SubContent> <S.TextError>Você não está associado{'\n'} a nenhuma empresa.</S.TextError> <ButtonCompany title="Continuar sem identificação" onPress={() => navigate('Home')} /> </S.Content> )} {!loading && empresas && empresas?.length > 0 && ( <S.Content> <S.Title>Escolha</S.Title> <S.Description>Deseja se conectar com qual empresa?</S.Description> { empresas.length > 0 && empresas?.map((item, index) => ( <ButtonCompany key={index} title={item.razaoSocial} onPress={() => alert('ok')} /> )) } </S.Content> )} <S.Button onPress={null}> <S.TextButton>Central de Atendimento</S.TextButton> </S.Button> </S.SafeArea> </S.Gradient> </S.Container> ) }
注意:如果你的getAllEnterprises是封装的fetch/axios请求,需要调整封装逻辑支持传入signal参数,fetch原生支持该参数,axios可以将signal放在请求配置项中传入。
如果暂时无法修改请求封装,可以使用临时的挂载标记方案作为过渡:
useEffect(() => { let isMounted = true; getAllEnterprises(token!, '7795297') .then((res: any) => { if (isMounted) { setEmpresas(res); } }) .catch((error: any) => { alert("Erro ao buscar empresas" + error); }) .finally(() => { if (isMounted) { setLoading(false); } }); return () => { isMounted = false; } }, []);
该方案只是阻止了卸载后的状态更新,并没有真正终止请求,优先级低于AbortController方案。
内容的提问来源于stack exchange,提问作者LayTexas

