React抽离复用带动画与错误捕获的异步逻辑遇Hook错误求方案
React异步请求+动画逻辑复用方案(解决Hook调用错误)
你之前的错误根源是:把useCallback这类Hook放到了普通函数里,React要求Hooks只能在函数组件或自定义Hook的顶层调用,普通函数不属于合法的Hook调用上下文,因此触发了「Invalid hook call」错误。以下是两种可通用复用的拆分方案:
方案一:封装为自定义Hook(推荐,符合React规范)
自定义Hook是React官方推荐的组件逻辑复用方式,它可以合法调用其他Hooks,同时保留状态和依赖追踪能力。
自定义Hook实现
// hooks/useAsyncWithAnimation.js import { useCallback } from 'react'; // 接收异步函数数组、加载状态控制函数,返回触发请求的函数 export function useAsyncWithAnimation(asyncFns, setLoading) { const triggerRequest = useCallback(async () => { try { setLoading(true); // 批量执行异步请求 const results = await Promise.all(asyncFns.map(fn => fn())); return results; } catch (error) { // 统一错误处理,也可抛出让组件自行处理 console.error('批量请求失败:', error); throw error; } finally { // 无论成功失败都关闭动画 setLoading(false); } }, [asyncFns, setLoading]); // 依赖数组确保函数更新时机正确 return triggerRequest; }
组件中使用
// 业务组件 import { useEffect, useState } from 'react'; import { useAsyncWithAnimation } from './hooks/useAsyncWithAnimation'; import { getGroupData, getPolicyData } from './api'; function MyComponent() { const [loading, setLoading] = useState(false); const [groupData, setGroupData] = useState(null); const [policyData, setPolicyData] = useState(null); // 传入要执行的异步请求和加载状态控制函数 const fetchData = useAsyncWithAnimation( [getGroupData, getPolicyData], setLoading ); useEffect(() => { // 组件挂载时触发请求 const loadData = async () => { try { const [groupRes, policyRes] = await fetchData(); setGroupData(groupRes); setPolicyData(policyRes); } catch (err) { // 组件层面的自定义错误处理 } }; loadData(); }, [fetchData]); return ( <div> {loading && <div className="loading-spinner">加载中...</div>} {groupData && <div>分组数据:{JSON.stringify(groupData)}</div>} {policyData && <div>政策数据:{JSON.stringify(policyData)}</div>} </div> ); }
如果需要支持动态传参(比如每次请求参数不同),可调整Hook为接收生成异步函数数组的回调:
export function useAsyncWithAnimation(setLoading) { const triggerRequest = useCallback(async (getAsyncFns) => { try { setLoading(true); const asyncFns = getAsyncFns(); const results = await Promise.all(asyncFns.map(fn => fn())); return results; } catch (error) { console.error('请求失败:', error); throw error; } finally { setLoading(false); } }, [setLoading]); return triggerRequest; } // 组件内调用示例 useEffect(() => { const loadData = async () => { const [groupRes] = await fetchData(() => [() => getGroupData('param1')]); setGroupData(groupRes); }; loadData(); }, [fetchData]);
方案二:封装为纯工具函数(适合简单场景)
如果不想用自定义Hook,可把动画控制、Promise.all的逻辑抽成纯工具函数,组件内保留Hook的调用(比如useCallback、useEffect)。
工具函数实现
// utils/asyncUtils.js export async function fetchWithAnimation(asyncFns, setLoading) { try { setLoading(true); const results = await Promise.all(asyncFns.map(fn => fn())); return results; } catch (error) { console.error('请求失败:', error); throw error; } finally { setLoading(false); } }
组件中使用
// 业务组件 import { useEffect, useState, useCallback } from 'react'; import { fetchWithAnimation } from './utils/asyncUtils'; import { getGroupData, getPolicyData } from './api'; function MyComponent() { const [loading, setLoading] = useState(false); // 用useCallback包裹请求函数,确保依赖变化时更新 const fetchData = useCallback(async () => { await fetchWithAnimation([getGroupData, getPolicyData], setLoading); }, [setLoading]); useEffect(() => { fetchData(); }, [fetchData]); return ( <div> {loading && <div className="loading-animation">加载中...</div>} {/* 渲染内容 */} </div> ); }
内容的提问来源于stack exchange,提问作者hhpr98
相关产品推荐
相关产品推荐

