React+tRPC条件调用hooks报错:渲染的hooks数量不一致
解决依赖前接口响应的tRPC查询调用问题
针对你遇到的hooks调用顺序不一致导致的错误,这里有几个实用的处理方式,不用合并接口也能搞定:
1. 用enabled参数控制后续查询的执行
tRPC的useQuery支持enabled选项,你可以把所有查询都放在组件顶层,然后用前一个查询的状态来决定后续查询是否执行。这样既遵守了hooks必须顶层调用的规则,又能实现依赖前接口响应的逻辑。
举个例子:
import { api } from '../utils/api'; const QuestionsPage = () => { // 第一个查询:获取基础数据 const { data: firstData, isSuccess: firstSuccess } = api.firstQuery.useQuery(); // 第二个查询:依赖firstData,只有第一个查询成功且有数据时才执行 const { data: secondData } = api.secondQuery.useQuery( { id: firstData?.id }, { enabled: firstSuccess && !!firstData } ); // 第三个查询:依赖secondData,同理控制enabled const { data: thirdData } = api.thirdQuery.useQuery( { key: secondData?.key }, { enabled: !!secondData } ); // 渲染逻辑 if (!firstData) return <div>加载中...</div>; if (!secondData) return <div>加载第二步...</div>; return ( <div> {/* 渲染数据 */} </div> ); };
所有hooks都在顶层,每次渲染调用顺序一致,enabled参数会自动跳过未满足条件的查询,不会触发不必要的请求,也不会出现hooks数量不一致的错误。
2. 用useEffect结合tRPC客户端手动调用
如果依赖逻辑特别复杂(比如多条件分支、动态决定调用接口),可以用useEffect直接调用tRPC客户端方法,自己管理数据、加载、错误状态。这种方式灵活性更高,完全避开hooks顺序问题。
例子:
import { useState, useEffect } from 'react'; import { api } from '../utils/api'; const QuestionsPage = () => { const [firstData, setFirstData] = useState<FirstData | null>(null); const [secondData, setSecondData] = useState<SecondData | null>(null); const [thirdData, setThirdData] = useState<ThirdData | null>(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState<string | null>(null); // 获取第一个数据 useEffect(() => { const fetchData = async () => { try { const res = await api.firstQuery.query(); setFirstData(res); } catch (err) { setError('获取第一个数据失败'); } finally { setIsLoading(false); } }; fetchData(); }, []); // 依赖firstData获取第二个数据 useEffect(() => { if (!firstData) return; const fetchSecond = async () => { try { const res = await api.secondQuery.query({ id: firstData.id }); setSecondData(res); } catch (err) { setError('获取第二个数据失败'); } }; fetchSecond(); }, [firstData]); // 依赖secondData获取第三个数据 useEffect(() => { if (!secondData) return; const fetchThird = async () => { try { const res = await api.thirdQuery.query({ key: secondData.key }); setThirdData(res); } catch (err) { setError('获取第三个数据失败'); } }; fetchThird(); }, [secondData]); if (isLoading) return <div>加载中...</div>; if (error) return <div>{error}</div>; if (!firstData || !secondData || !thirdData) return <div>数据不完整</div>; return ( <div> {/* 渲染内容 */} </div> ); };
这种方式适合复杂业务场景,但需要自己维护状态,不像useQuery那样自动缓存、重试,常规依赖查询优先用第一种方法。
3. 封装成自定义Hook
如果多个组件都有类似的依赖查询逻辑,可以把查询逻辑封装成自定义Hook,内部用enabled参数控制,对外暴露统一的数据和状态,保持组件代码整洁。
例子:
import { api } from '../utils/api'; const useChainedQueries = () => { const { data: firstData, isSuccess: firstSuccess } = api.firstQuery.useQuery(); const { data: secondData } = api.secondQuery.useQuery( { id: firstData?.id }, { enabled: firstSuccess && !!firstData } ); const { data: thirdData } = api.thirdQuery.useQuery( { key: secondData?.key }, { enabled: !!secondData } ); return { firstData, secondData, thirdData, isLoading: !firstSuccess || !secondData || !thirdData, }; }; // 在组件中使用 const QuestionsPage = () => { const { firstData, secondData, thirdData, isLoading } = useChainedQueries(); if (isLoading) return <div>加载中...</div>; return ( <div> {/* 渲染数据 */} </div> ); };
自定义Hook同样遵守hooks规则,把复杂查询逻辑抽离,组件只需要关心数据使用。
内容的提问来源于stack exchange,提问作者Lotan
相关产品推荐
相关产品推荐

