React自定义数据获取Hook:如何解决loading与error状态不一致问题
解决React自定义Hook手动模式下loading状态间隙问题
问题核心
手动模式下,Hook初始loading为false,且没有状态标记是否发起过请求,导致组件第一次渲染时,会错误显示“加载完成”的内容(如示例中的Hello world!),直到useEffect触发fetch后loading才变为true。本质是缺少“未发起请求”的状态区分,当前loading仅能表示“是否正在加载”,无法区分“从未加载过”和“加载完成”两种状态。
解决方案
方案1:新增hasFetched状态标记请求触发状态
在Hook中新增hasFetched状态,明确标记是否已经发起过至少一次数据请求。手动模式下初始值为false,自动模式下初始值为true(因为自动模式会在挂载时立即发起请求)。
修改后的Hook代码:
const useOrders = (manual: boolean) => { const [{ loading, error, data, hasFetched }, setState] = useState<{ loading: boolean, error: null | unknown, data: Order[], hasFetched: boolean }>({ loading: !manual, error: null, data: [], hasFetched: !manual // 自动模式默认已触发请求,手动模式初始未触发 }); const fetch = useCallback(async () => { setState(prev => ({ ...prev, loading: true, error: null, hasFetched: true // 发起请求时标记为已触发 })); return axios.get<Order[]>('/api/orders') .then(({ data }) => { setState(prev => ({ ...prev, loading: false, data })); return data; }) .catch(error => { setState(prev => ({ ...prev, loading: false, error })); throw error; }); }, []); useEffect(() => { !manual && fetch(); }, [manual]); return { fetch, loading, error, data, hasFetched }; }
组件渲染逻辑调整:
const Modal = () => { const { fetch, loading, data, error, hasFetched } = useOrders(true); useEffect(() => { isOpen && fetch(); }, [isOpen]); return ( <> {loading && <p>Loading...</p>} {/* 仅当已触发请求、未加载且无错误时,才显示内容 */} {!loading && hasFetched && !error && <p>Hello world!</p>} </> ); };
方案2:通过data初始值区分状态
如果不想新增额外状态,可以调整data的类型,将手动模式下的初始值设为undefined,用来表示“从未发起过请求”,请求完成后再设置为数组(空或有数据)。
修改后的Hook代码:
const useOrders = (manual: boolean) => { const [{ loading, error, data }, setState] = useState<{ loading: boolean, error: null | unknown, data: Order[] | undefined // 修改data类型为可选数组 }>({ loading: !manual, error: null, data: manual ? undefined : [] // 手动模式初始为undefined,自动模式初始为空数组 }); const fetch = useCallback(async () => { setState(prev => ({ ...prev, loading: true, error: null })); return axios.get<Order[]>('/api/orders') .then(({ data }) => { setState(prev => ({ ...prev, loading: false, data })); return data; }) .catch(error => { setState(prev => ({ ...prev, loading: false, error })); throw error; }); }, []); useEffect(() => { !manual && fetch(); }, [manual]); return { fetch, loading, error, data }; }
组件渲染逻辑调整:
const Modal = () => { const { fetch, loading, data, error } = useOrders(true); useEffect(() => { isOpen && fetch(); }, [isOpen]); return ( <> {loading && <p>Loading...</p>} {/* 仅当data存在(即已发起请求)、未加载且无错误时,才显示内容 */} {data !== undefined && !loading && !error && <p>Hello world!</p>} </> ); };
方案说明
两种方案都通过明确区分“未发起请求”和“加载完成”状态,解决了手动模式下的渲染间隙问题:
- 方案1语义更清晰,新增状态直接标记请求触发情况;
- 方案2无需额外状态,通过
data初始值区分,但需要调整类型定义。
内容的提问来源于stack exchange,提问作者gremo
相关产品推荐
相关产品推荐

