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

为多个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请求的生命周期,直接在全局统计加载状态和错误。这个方案的松耦合度最高,适合所有请求的统一管理。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 04:35:40