React+GraphQL多API调用优化:减少useEffect与组件重渲染
React+GraphQL组件优化:减少useEffect与重渲染
你的场景与问题
你在React+GraphQL项目中未使用Redux这类状态管理工具,把API调用封装在了自定义Hook里,但组件内需要写大量useEffect来触发请求和更新状态,担心过度使用useEffect导致重渲染过多,想知道把请求逻辑抽成独立函数后放进单个useEffect是不是更优的实践。
先看你的现有代码
自定义Hook代码
// 注:原代码存在函数定义不完整问题,推测完整结构如下 function useGetSomeData1() { const [getSomeData, { data: getSomeDataData, loading: getSomeDataLoading, error: getSomeDataError }] = useLazyQuery( GET_SOME_DATA2, { client: dataClient, fetchPolicy: "cache-and-network", nextFetchPolicy: "cache-first", } ); return { getSomeData, getSomeDataData, getSomeDataLoading, getSomeDataError, }; } export default function useGetSomeData() { const [getSomeData2, { data: getSomeDataData2, loading: getSomeDataLoading2, error: getSomeDataError2 }] = useLazyQuery( GET_SOME_DATA, { client: dataClient, fetchPolicy: "cache-and-network", nextFetchPolicy: "cache-first", } ); return { getSomeData2, getSomeDataData2, getSomeDataLoading2, getSomeDataError2, }; }
组件内原使用方式
useEffect(()=>{ getSomeData({ variables }) },[]) useEffect(()=>{ getSomeData2({ variables }) },[]) useEffect(()=>{ if( getSomeDataData2 && !getSomeDataLoading2 && !getSomeDataError2){ setState(getSomeDataData2?.data) } },[getSomeDataData2 ,getSomeDataLoading2 ,getSomeDataError2]) useEffect(()=>{ if( getSomeDataData && !getSomeDataLoading && !getSomeDataError){ setState(getSomeDataData2?.data) // 注:这里疑似笔误,应该是getSomeDataData?.data } },[getSomeDataData ,getSomeDataLoading ,getSomeDataError])
你设想的优化方式
const fetchData = () => { getSomeData({ variables }) } const fetchData2 = () => { getSomeData2({ variables }) } useEffect(()=>{ fetchData() fetchData2() },[]) useEffect(()=>{ if( getSomeDataData2 && !getSomeDataLoading2 && !getSomeDataError2){ setState(getSomeDataData2?.data) } },[getSomeDataData2 ,getSomeDataLoading2 ,getSomeDataError2]) useEffect(()=>{ if( getSomeDataData && !getSomeDataLoading && !getSomeDataError){ setState(getSomeDataData2?.data) // 同样疑似笔误 } },[getSomeDataData ,getSomeDataLoading ,getSomeDataError])
优化方案解答
1. 单个useEffect触发多个请求确实更优
把多个初始化请求合并到同一个空依赖useEffect里,不会增加重渲染次数,反而能减少组件挂载时的Effect执行次数(从2次变成1次),代码也更整洁。这种做法是React组件初始化逻辑的常规优化,完全没问题。
2. 进一步减少重渲染的建议
(1)先修复笔误,避免无效状态更新
你的状态更新逻辑里有明显笔误:setState(getSomeDataData2?.data)应该对应getSomeDataData?.data,错误的赋值会导致不必要的状态更新,进而触发无意义的重渲染,先把这个问题解决。
(2)把状态更新逻辑内聚到自定义Hook中
既然已经封装了自定义Hook,完全可以把数据到组件状态的映射逻辑也放进Hook里,组件直接拿到处理好的数据,不用再写额外的useEffect:
// 优化后的自定义Hook function useGetSomeData1() { const [processedData, setProcessedData] = useState(null); const [getSomeData, { data: getSomeDataData, loading: getSomeDataLoading, error: getSomeDataError }] = useLazyQuery( GET_SOME_DATA2, { client: dataClient, fetchPolicy: "cache-and-network", nextFetchPolicy: "cache-first", onCompleted: (data) => { // 直接在请求完成时更新处理后的数据 if (data) setProcessedData(data?.data); }, onError: () => { // 错误处理可在这里统一处理,比如提示用户 } } ); return { getSomeData, data: processedData, loading: getSomeDataLoading, error: getSomeDataError, }; } // 同理优化useGetSomeData export default function useGetSomeData() { const [processedData, setProcessedData] = useState(null); const [getSomeData2, { data: getSomeDataData2, loading: getSomeDataLoading2, error: getSomeDataError2 }] = useLazyQuery( GET_SOME_DATA, { client: dataClient, fetchPolicy: "cache-and-network", nextFetchPolicy: "cache-first", onCompleted: (data) => { if (data) setProcessedData(data?.data); } } ); return { getSomeData2, data: processedData, loading: getSomeDataLoading2, error: getSomeDataError2, }; }
这样组件里只需要调用请求函数,直接使用Hook返回的data即可,完全省去了状态更新的useEffect:
const { getSomeData, data: data1, loading: loading1, error: error1 } = useGetSomeData1(); const { getSomeData2, data: data2, loading: loading2, error: error2 } = useGetSomeData(); useEffect(() => { getSomeData({ variables }); getSomeData2({ variables }); }, []); // 直接使用data1和data2,不用再手动setState
(3)用useMemo缓存计算结果
如果组件中需要基于返回的数据做计算,可以用useMemo缓存计算结果,避免每次渲染都重新计算:
const combinedData = useMemo(() => { if (!data1 || !data2) return null; // 基于data1和data2的合并/计算逻辑 return [...data1, ...data2]; }, [data1, data2]);
总结
- 把多个初始化请求合并到单个useEffect里是靠谱的优化,既能减少挂载时的Effect执行次数,代码也更清爽。
- 更核心的优化是把状态更新逻辑塞进自定义Hook里,直接在Hook里处理数据到状态的映射,组件里就不用再写一堆状态更新的Effect了,从根源减少重渲染的触发。
- 记得先把代码里的笔误改了,不然错误的状态更新会平白无故引发重渲染。
内容的提问来源于stack exchange,提问作者Caolan Fanning
相关产品推荐
相关产品推荐

