自定义Hook嵌套TanStack Query Hook的ESLint校验问题及模式合理性咨询
自定义Hook嵌套TanStack Query Hook的ESLint校验问题及模式合理性咨询
你遇到的这个ESLint报错,本质上是碰了React Hook的核心红线——Hook必须在组件或自定义Hook的顶层调用,且每次渲染的调用顺序必须完全一致。咱们一步步拆解问题:
为什么会报错?
你现在的写法是把useMutation/useQuery这类Hook包裹在普通函数里(比如CheckLogin),然后在组件中先解构出这个函数,再动态调用它来触发Hook执行。这相当于把Hook的调用时机交给了动态执行的普通函数:
- 每次组件渲染时,
useAuthQuery()都会返回新的CheckLogin函数实例 - 调用
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;
这种修改的好处
- 完全符合React Hook的规则,ESLint校验直接通过
- 依然保持了所有Auth相关Query/Mutation的聚合性,不需要拆成多个单独的导出文件
- React可以稳定跟踪每个Hook的状态,避免了动态调用带来的潜在问题
内容来源于stack exchange
相关产品推荐
相关产品推荐

