You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 12:20:22