React+TypeScript中useEffect卸载清理状态与设置状态冲突问题
问题
在React+TypeScript项目中,点击客户端条目会设置对应的clientIdForRegister和clientNameForRegister,用于后续API请求;同时需要在用户离开页面时清理这两个状态,但之前的实现出现冲突:
- 点击客户端条目设置状态的代码:
const setClient = () => { setClientIdForRegister(id) setClientNameForRegister(nome) } return ( <ClientSearchResultInfos onClick={setClient}> <div className="client_and_email"> <h2>{nome}</h2> <span>{email}</span> </div> <CaretRight size={30} /> </ClientSearchResultInfos> )
- API请求使用状态的代码:
const handleRegisterReceipt = async (data: FormDataProps) => { try { const dataToSend = { cliente: { id: clientIdForRegister, }, dataPagamento: data.dataPagamento, valor: data.valor, } const response = await api.post('/pagamentos', dataToSend) } }
- 点击客户端后同步表单输入框的代码:
useEffect(() => { const getClientName = async () => { if (clientNameForRegister) { setValue('cliente', clientNameForRegister) } } getClientName() }, [setValue, clientNameForRegister])
尝试过两种清理状态的写法都有问题:
- 父组件无依赖的useEffect返回清理函数,导致每次重渲染都执行清理,
clientIdForRegister始终为null:
// 父组件: const cleanUp = () => { setClientNameForRegister(null) setClientIdForRegister(null) } useEffect(() => { return cleanUp })
- 使用
isMountedRef标记挂载状态,但卸载时无法清理状态:
// 父组件: const isMountedRef = useRef<boolean>(true) useEffect(() => { return () => { isMountedRef.current = false if (isMountedRef.current) { setClientNameForRegister(null) setClientIdForRegister(null) } } })
需要实现:用户离开页面时清理状态,同时正常发送API请求时clientIdForRegister有效。
解决方案
方案1:给父组件的清理useEffect添加空依赖数组
这是最简单直接的方法,空依赖数组的useEffect仅在组件挂载时执行一次,返回的清理函数只会在组件卸载时触发,不会在每次重渲染时执行,完美解决状态被频繁重置的问题:
// 父组件 useEffect(() => { return () => { setClientNameForRegister(null); setClientIdForRegister(null); }; }, []); // 空依赖数组,仅在组件卸载时执行清理逻辑
方案2:结合路由监听精准控制清理时机
如果是通过路由切换离开页面,可以监听路由变化,仅在离开当前页面路由时清理状态(适用于react-router v6):
import { useLocation } from 'react-router-dom'; // 父组件 const location = useLocation(); // 假设当前页面的路由路径为 '/register-receipt' const currentPagePath = '/register-receipt'; useEffect(() => { return () => { // 当路由切换到其他页面时,清理状态 if (location.pathname !== currentPagePath) { setClientNameForRegister(null); setClientIdForRegister(null); } }; }, [location]);
方案3:修正isMountedRef的使用方式
之前的写法错误在于清理函数中先将isMountedRef.current设为false,再判断,导致清理逻辑永远不执行。正确的做法是:
- 用
isMountedRef标记组件是否处于挂载状态 - 在设置状态前检查标记,避免卸载后触发状态更新
- 清理函数直接执行状态重置(组件卸载后设置状态不会触发无效重渲染,无需额外判断)
// 父组件 const isMountedRef = useRef<boolean>(true); // 标记组件卸载状态 useEffect(() => { return () => { isMountedRef.current = false; }; }, []); // 修改设置状态的函数,仅在组件挂载时更新状态 const setClient = () => { if (isMountedRef.current) { setClientIdForRegister(id); setClientNameForRegister(nome); } }; // 组件卸载时清理状态 useEffect(() => { return () => { setClientNameForRegister(null); setClientIdForRegister(null); }; }, []);
内容的提问来源于stack exchange,提问作者Pedro Schinke
相关产品推荐
相关产品推荐

