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

自定义Hook嵌套TanStack Query Hook的ESLint校验问题及模式合理性咨询

自定义Hook嵌套TanStack Query Hook的ESLint校验问题及模式合理性咨询

你遇到的这个ESLint报错,本质上是碰了React Hook的核心红线——Hook必须在组件或自定义Hook的顶层调用,且每次渲染的调用顺序必须完全一致。咱们一步步拆解问题:

为什么会报错?

你现在的写法是把useMutation/useQuery这类Hook包裹在普通函数里(比如CheckLogin),然后在组件中先解构出这个函数,再动态调用它来触发Hook执行。这相当于把Hook的调用时机交给了动态执行的普通函数:

  1. 每次组件渲染时,useAuthQuery()都会返回新的CheckLogin函数实例
  2. 调用CheckLogin()时才会执行内部的useMutation,这违反了Hook必须在顶层稳定调用的规则

React需要通过固定的调用顺序来跟踪每个Hook的状态,这种动态调用的方式会让React无法保证状态跟踪的稳定性,所以ESLint的react-compiler规则会直接拦截。

这算不算反模式?

严格来说,这种“在自定义Hook中返回Hook函数”的方式属于Hook的不当使用——虽然功能上能跑,但确实不符合Hook的设计规范,长期来看可能埋下隐患:比如在React并发渲染模式下出现状态不同步,或者遇到难以排查的Hook状态跟踪问题。

不用拆成单独导出的解决方案

其实你不需要把所有代码拆回单独导出的Hook,只需要调整内部逻辑,把Hook的执行移到useAuthQuery的顶层,直接返回Hook的结果对象,而不是返回Hook函数。这样既保留了代码的聚合性,又完全符合Hook规则:

export const useAuthQuery = () => {
  const { auth } = useAuth();
  const { customKy } = useKy();

  const getUser = async (login: string) => {
    const users: user[] = await customKy.get(USERS).json();
    const user = users.find((user) => user.username === login);
    return user;
  };

  // 直接在顶层调用useQuery,返回查询结果
  const userQuery = useQuery({
    queryKey: ["user", auth?.username],
    queryFn: async () => {
      if (!auth?.username) return;
      const user = await getUser(auth.username);
      return user;
    },
    enabled: !!auth?.username, // 只有当auth存在时才执行查询
  });

  // 直接在顶层调用useMutation,返回Mutation实例
  const checkLogin = useMutation({
    mutationKey: ["CheckLogin"],
    mutationFn: async (login: string): Promise<CheckLoginResponse | undefined> => {
      const user = await getUser(login);
      console.log(user);
      if (!user) throw new Error("User not found!");
      return user;
    },
    gcTime: 0,
  });

  const loginByPassword = useMutation({
    mutationFn: async (credentials: { login: string; password: string }): Promise<loginResponse> => {
      const user = await getUser(credentials.login);
      if (!user) throw new Error("User not found!");
      return await customKy
        .post(LOGIN, {
          json: { email: user?.email, password: credentials.password },
        })
        .json();
    },
    gcTime: 0,
  });

  const loginByCode = useMutation({
    mutationFn: async (credentials: { login: string; code: string }) => {
      const user = await getUser(credentials.login);
      if (Number(user?.otp) === Number(credentials.code)) {
        return customKy
          .post<loginResponse>(LOGIN, {
            json: { email: user?.email, password: user?.realPassword },
          })
          .json();
      } else {
        throw new Error("wrong code");
      }
    },
    gcTime: 0,
  });

  const requestCode = useMutation({
    mutationFn: async (codeData: { otp: number; login: string }) => {
      const user = await getUser(codeData.login);
      return customKy.patch(`${USERS}/${user?.id}`, {
        json: { otp: codeData.otp },
      });
    },
    gcTime: 0,
  });

  // 直接返回各个Hook的执行结果
  return {
    userQuery,
    checkLogin,
    loginByPassword,
    loginByCode,
    requestCode,
  };
};

然后在组件中直接解构使用,不需要再调用函数:

// 组件中使用方式
const { checkLogin } = useAuthQuery();
const { 
  data: CheckLoginData, 
  mutateAsync: CheckLoginMutation, 
  isPending: isCheckLoginPending, 
  reset 
} = checkLogin;

这种修改的好处

  1. 完全符合React Hook的规则,ESLint校验直接通过
  2. 依然保持了所有Auth相关Query/Mutation的聚合性,不需要拆成多个单独的导出文件
  3. React可以稳定跟踪每个Hook的状态,避免了动态调用带来的潜在问题

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 12:08:00