React 中如何在渲染映射中安全使用 Hook 返回的函数

在 react 中,不能在组件外部或条件逻辑中调用 hook,因此无法将 `usefirebaseauth()` 等 hook 返回的函数(如 `signinwithapple`)直接写入全局常量数组。正确做法是将数据结构移入组件内部或封装为自定义 hook,确保 hook 调用符合规则。

React 的 Hooks 规则(尤其是「只能在顶层调用 Hook」)意味着:所有 Hook 必须在组件函数体最外层、无条件执行。你最初尝试将 signInWithApple 直接赋值给模块级常量 socialAuthMethodsMap,违反了这一规则——因为此时 useFirebaseAuth() 尚未被调用,signInWithApple 甚至不存在。

✅ 正确方案一:在组件内声明映射数据

将 socialAuthMethodsMap 定义在组件作用域中,并在 Hook 调用之后立即使用其返回值:

import { FunctionComponent, MouseEventHandler } from 'react';
import { IconProps } from 'react-feather';
import { useFirebaseAuth } from './hooks/useFirebaseAuth'; // 假设路径
import { SocialAuthButton } from './components/SocialAuthButton';

type TSocialAuthMethodData = {
  code: string;
  logo?: string | FunctionComponent;
  onClick: MouseEventHandler;
};

const MyAuthPage = () => {
  const { signInWithApple } = useFirebaseAuth();

  const socialAuthMethodsMap: TSocialAuthMethodData[] = [
    {
      code: 'apple',
      logo: '/assets/icons/social/apple.svg',
      onClick: signInWithApple, // ✅ 此时 signInWithApple 已有效
    },
    {
      code: 'google',
      logo: '/assets/icons/social/google.svg',
      onClick: () => console.log('Google auth placeholder'),
    },
    {
      code: 'github',
      logo: '/assets/icons/social/github.svg',
      onClick: () => console.log('GitHub auth placeholder'),
    },
  ];

  return (
    
      {socialAuthMethodsMap.map((method) => (
        
      ))}
    
  );
};

export default MyAuthPage;
? 提示:onClick 回调需保持稳定(避免每次渲染都新建匿名函数),此处 signInWithApple 是 Hook 返回的稳定引用,符合最佳实践。

✅ 正确方案二:封装为自定义 Hook(推荐复用场景)

当多个组件需要相同认证方法配置时,可抽象为 useSocialAuthData 自定义 Hook:

// hooks/useSocialAuthData.ts
import { useFirebaseAuth } from './useFirebaseAuth';
import { TSocialAuthMethodData } from '../types';

export const useSocialAuthData = (): TSocialAuthMethodData[] => {
  const { signInWithApple } = useFirebaseAuth();

  return [
    {
      code: 'apple',
      logo: '/assets/icons/social/apple.svg',
      onClick: signInWithApple,
    },
    {
      code: 'google',
      logo: '/assets/icons/social/google.svg',
      onClick: () => alert('Google login not implemented yet'),
    },
    {
      code: 'github',
      logo: '/assets/icons/social/github.svg',
      onClick: () => alert('GitHub login not implemented yet'),
    },
  ];
};

在组件中使用:

const MyAuthPage = () => {
  const socialAuthMethodsMap = useSocialAuthData(); // ✅ Hook 调用合规

  return (
    
      {socialAuthMethodsMap.map((method) => (
        
      ))}
    
  );
};

⚠️ 注意事项

  • 禁止 map、filter、条件语句、普通函数内部调用 Hook;
  • ✅ 自定义 Hook 名称必须以 use 开头(如 useSocialAuthData),这是 React 识别 Hook 的约定;
  • ? 若 useFirebaseAuth 内部依赖 useEffect 或状态更新,请确保其自身也严格遵守 Hook 规则;
  • ? 对于动态图标(如 FunctionComponent),建议统一包装为 ReactNode 类型,提升灵活性。

通过以上任一方式,你既能保持代码组织清晰,又能完全遵守 React 的 Hooks 规范,让认证按钮逻辑安全、可维护、可复用。