解决自定义Hook重复执行问题:基于createAsyncThunk的数据请求
问题分析与解决方案
你的自定义Hook执行两次的核心原因有两个:一是没有用useEffect包裹异步请求逻辑,导致组件每次渲染都会触发请求;二是React 18的严格模式在开发环境会自动执行两次挂载逻辑,用来检测潜在的副作用问题。
下面是具体的修复方案:
1. 重构自定义Hook(正确写法)
将异步请求逻辑放入useEffect中,用useState管理数据、加载状态和错误信息,符合React Hook的规则:
import { useDispatch } from 'react-redux'; import { useEffect, useState } from 'react'; import { AppDispatch } from '../store'; // 替换为你的store类型路径 import fetchTransactionsHistory from '../features/transactions/transactionSlice'; // 替换为你的thunk路径 export default function useFetchTransactionHistory() { const dispatch = useDispatch<AppDispatch>(); const [data, setData] = useState<any>(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState<Error | null>(null); useEffect(() => { // 标志位:防止组件卸载或重复渲染时更新状态 let isMounted = true; const fetchData = async () => { try { setIsLoading(true); // 使用unwrap()解析createAsyncThunk的返回值:成功时取数据,失败时抛出错误 const res = await dispatch(fetchTransactionsHistory()).unwrap(); if (isMounted && res.code === 200) { setData(res.data); } } catch (err) { if (isMounted) { setError(err as Error); console.error(err); } } finally { if (isMounted) { setIsLoading(false); } } }; fetchData(); // 组件卸载时重置标志位 return () => { isMounted = false; }; }, [dispatch]); // dispatch是Redux提供的稳定函数,可安全作为依赖项 return { data, isLoading, error }; }
2. 组件中正常使用Hook
export default function TransactionHistory() { const { data, isLoading } = useFetchTransactionHistory(); if (isLoading) return <div>加载中...</div>; return( <div>{data ? JSON.stringify(data) : '暂无交易记录'}</div> ); }
关键说明
- useEffect的作用:确保请求只在组件挂载时执行一次,后续组件重新渲染不会重复触发请求。
- unwrap()方法:createAsyncThunk返回的Promise包含Redux的action信息,
unwrap()可以直接获取API返回的原始数据,或在请求失败时抛出错误,方便统一处理。 - 严格模式的两次渲染:React 18开发环境的两次挂载是调试机制,生产环境不会触发。如果要避免开发环境的重复状态更新,可以用上述代码中的
isMounted标志位过滤无效操作,但请求本身还是会发两次(这是严格模式的特性,无需强行阻止)。
内容的提问来源于stack exchange,提问作者keenlabi
相关产品推荐
相关产品推荐

