为多个React Apollo查询实现统一顶层加载/错误界面
如何在React Native + Apollo 2.1中实现顶层组件全局监听下层查询状态
嘿,这个问题我之前在使用Apollo 2.x的时候也遇到过——默认的Query组件确实没给全局状态监听留太多现成的口子,但咱们可以通过几个优雅的松耦合方案来实现顶层统一处理加载/错误提示的需求,下面给你详细拆解:
方案1:自定义高阶组件(HOC)包装Query,通过Context同步全局状态
这个方案的核心是把Apollo的Query组件包装一层,在包装组件里处理加载、错误状态,再通过React Context把这些状态同步到顶层组件。好处是可以精准控制每个Query的状态上报,同时保持组件之间的松耦合。
步骤1:创建全局状态Context
import React, { createContext, useContext, useState, useCallback } from 'react'; // 创建全局查询状态的Context const GlobalQueryStateContext = createContext({ loadingCount: 0, hasError: false, resetError: () => {}, setLoadingCount: () => {}, setHasError: () => {}, }); // 自定义Hook,方便组件调用 export const useGlobalQueryState = () => useContext(GlobalQueryStateContext);
步骤2:封装全局Query组件
import { Query } from 'react-apollo'; import { useEffect } from 'react'; export const GlobalQuery = ({ children, ...props }) => { const { loadingCount, setLoadingCount, setHasError } = useGlobalQueryState(); return ( <Query {...props} onCompleted={() => { // 查询完成后减少加载计数,重置错误状态 setLoadingCount(prev => Math.max(prev - 1, 0)); setHasError(false); // 保留原组件的onCompleted逻辑 if (props.onCompleted) props.onCompleted(); }} onError={(error) => { // 查询出错后减少加载计数,标记全局错误 setLoadingCount(prev => Math.max(prev - 1, 0)); setHasError(true); // 保留原组件的onError逻辑 if (props.onError) props.onError(error); }} > ({ loading, error, data }) => { // 监听加载状态变化,更新全局计数 useEffect(() => { if (loading) { setLoadingCount(prev => prev + 1); } }, [loading]); // 监听错误状态变化,标记全局错误 useEffect(() => { if (error) { setHasError(true); } }, [error]); return children({ loading, error, data }); } </Query> ); };
步骤3:顶层组件提供Context并渲染全局提示
export const AppProvider = ({ children }) => { const [loadingCount, setLoadingCount] = useState(0); const [hasError, setHasError] = useState(false); const resetError = useCallback(() => { setHasError(false); }, []); const contextValue = { loadingCount, hasError, setLoadingCount, setHasError, resetError, }; return ( <GlobalQueryStateContext.Provider value={contextValue}> {/* 全局加载提示:只要有查询在加载就显示 */} {loadingCount > 0 && ( <div style={{ position: 'fixed', top: 0, left: 0, right: 0, padding: 10, background: '#f0f0f0', textAlign: 'center' }}> 页面加载中... </div> )} {/* 全局错误提示:点击重试重置错误状态 */} {hasError && ( <div style={{ position: 'fixed', top: 0, left: 0, right: 0, padding: 10, background: '#ffcccc', textAlign: 'center' }}> 页面出错,点击重试 <button onClick={resetError} style={{ marginLeft: 10 }}>重试</button> </div> )} {children} </GlobalQueryStateContext.Provider> ); };
使用方式
把你原来代码里的<Query>替换成<GlobalQuery>即可,比如:
<GlobalQuery query={GET_AUTHORS}> {(loading, errors, data) => <CategoryPager categories={data.authors}> {(author) => <AuthorDetails author={author} />} </CategoryPager>} </GlobalQuery>
方案2:用Apollo Link拦截全局请求(更底层,无需修改组件)
如果不想修改任何下层的Query组件,可以利用Apollo Link的拦截能力,监听所有GraphQL请求的生命周期,直接在全局统计加载状态和错误。这个方案的松耦合度最高,适合所有请求的统一管理。
步骤1:创建自定义Status Link
import { ApolloLink, Observable } from 'apollo-link'; import { createHttpLink } from 'apollo-link-http'; import { ApolloClient } from 'apollo-client'; import { InMemoryCache } from 'apollo-cache-inmemory'; // 全局状态变量(也可以用Context管理,这里为了简化用全局变量) let loadingCount = 0; let hasGlobalError = false; let stateChangeCallback = () => {}; // 自定义Link,拦截所有请求 const statusLink = new ApolloLink((operation, forward) => { // 请求开始:增加加载计数,重置错误状态 loadingCount++; hasGlobalError = false; stateChangeCallback({ loading: loadingCount > 0, hasError: hasGlobalError }); return new Observable(observer => { const subscription = forward(operation).subscribe({ next: (result) => { // 请求成功完成:减少加载计数 loadingCount--; stateChangeCallback({ loading: loadingCount > 0, hasError: hasGlobalError }); observer.next(result); }, error: (error) => { // 请求出错:减少加载计数,标记全局错误 loadingCount--; hasGlobalError = true; stateChangeCallback({ loading: loadingCount > 0, hasError: hasGlobalError }); observer.error(error); }, complete: () => { // 请求完成(不管成功失败):确保加载计数正确 if (loadingCount > 0) { loadingCount--; stateChangeCallback({ loading: loadingCount > 0, hasError: hasGlobalError }); } observer.complete(); }, }); // 请求取消时更新状态 return () => { loadingCount--; stateChangeCallback({ loading: loadingCount > 0, hasError: hasGlobalError }); subscription.unsubscribe(); }; }); }); // 创建Apollo Client,把自定义Link和HttpLink串联 const httpLink = createHttpLink({ uri: '你的GraphQL地址' }); export const client = new ApolloClient({ link: statusLink.concat(httpLink), cache: new InMemoryCache(), });
步骤2:顶层组件监听状态变化并渲染提示
import { useState, useEffect } from 'react'; import { client } from './apolloClient'; const GlobalStatusBar = () => { const [isLoading, setIsLoading] = useState(false); const [hasError, setHasError] = useState(false); useEffect(() => { // 注册状态变化回调 stateChangeCallback = ({ loading, hasError }) => { setIsLoading(loading); setHasError(hasError); }; // 组件卸载时清空回调 return () => { stateChangeCallback = () => {}; }; }, []); const handleRetry = () => { setHasError(false); // 可以调用Apollo Client的重试方法,或者让用户手动触发查询 client.retry(); }; return ( <> {isLoading && ( <div style={{ position: 'fixed', top: 0, left: 0, right: 0, padding: 10, background: '#f0f0f0', textAlign: 'center' }}> 页面加载中... </div> )} {hasError && ( <div style={{ position: 'fixed', top: 0, left: 0, right: 0, padding: 10, background: '#ffcccc', textAlign: 'center' }}> 页面出错,点击重试 <button onClick={handleRetry} style={{ marginLeft: 10 }}>重试</button> </div> )} </> ); };
使用方式
把GlobalStatusBar放在你的App顶层(比如AppProvider里),所有的Query请求都会被自动拦截,状态变化会同步到这个组件,完全不需要修改下层的任何代码。
方案对比
- 方案1:更灵活,可以针对单个Query控制是否上报状态,适合需要精细控制的场景;需要替换原有的Query组件,但改动很小。
- 方案2:完全无侵入,所有请求自动监听,适合全局统一管理的场景;状态统计基于请求层面,会自动包含
AuthorDetails这类子组件的内部查询,完美匹配你的需求。
两种方案都能实现松耦合的状态感知,你可以根据自己的实际场景选择。
内容的提问来源于stack exchange,提问作者D. Patrick
相关产品推荐
相关产品推荐

