如何全局处理Apollo查询与变更错误?400状态码问题排查
我太懂你这种头疼的情况了——格式不对的GraphQL查询/变更在React Native里直接抛出未处理的400网络错误,明明GraphiQL能正常返回错误详情,但本地就是抓不到,完全没法优雅处理对吧?别着急,下面给你几个亲测有效的全局处理方案,一步步解决这个问题:
全局处理Apollo查询/变更错误的实用方案
1. 用Apollo的onError链接做全局拦截(最推荐)
这是Apollo官方推荐的全局错误处理方式,通过apollo-link-error可以拦截所有GraphQL请求的错误,包括网络错误(比如400状态码)和GraphQL自身的字段错误。
实现步骤:
首先确保你已经安装了相关依赖(如果没装的话):
npm install @apollo/client apollo-link-error # 或者用yarn yarn add @apollo/client apollo-link-error
然后在创建Apollo Client实例时配置错误链接:
import { ApolloClient, InMemoryCache, HttpLink, from } from '@apollo/client'; import { onError } from '@apollo/client/link/error'; import { Alert } from 'react-native'; // 定义全局错误处理逻辑 const errorLink = onError(({ graphQLErrors, networkError }) => { // 处理GraphQL自身的错误(比如字段不存在、权限问题) if (graphQLErrors) { graphQLErrors.forEach(({ message, path }) => { console.log(`GraphQL字段错误 [路径: ${path}]: ${message}`); Alert.alert('查询错误', message); }); } // 处理网络错误(比如400、500状态码) if (networkError) { console.log('全局捕获网络错误:', networkError); // 针对400状态码做专门处理 if (networkError.statusCode === 400) { Alert.alert('请求错误', '查询/变更格式不正确,请检查语法'); // 如果你想阻止错误继续向上抛出(避免触发React的未处理错误),可以在这里做处理 } } }); // 组合所有链接并创建Client const httpLink = new HttpLink({ uri: '你的GraphQL端点地址' }); const client = new ApolloClient({ cache: new InMemoryCache(), link: from([errorLink, httpLink]) // 错误链接要放在http链接前面,才能拦截错误 });
这个方案的好处是全局生效,不管你用useQuery还是useMutation,所有请求的错误都会经过这里处理,而且能精准区分网络错误和GraphQL业务错误。
2. 用React错误边界做兜底捕获
如果Apollo的错误链接没拦住(比如某些特殊场景),可以用React的错误边界组件来兜底,避免应用直接崩溃,同时捕获未处理的错误。
实现错误边界组件:
import React from 'react'; import { View, Text, Alert } from 'react-native'; class ApolloErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError() { // 更新状态,显示错误UI return { hasError: true }; } componentDidCatch(error) { // 捕获到错误后做处理 console.log('错误边界捕获到未处理错误:', error); // 判断是否是Apollo的400网络错误 if (error.message.includes('Network error: Response not successful: Received status code 400')) { Alert.alert('请求失败', '查询格式有误,请检查后重试'); } } render() { if (this.state.hasError) { // 自定义错误提示UI return ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Text>抱歉,出现了一些问题,请稍后重试</Text> </View> ); } // 正常渲染子组件 return this.props.children; } } export default ApolloErrorBoundary;
然后在App的根组件里用它包裹ApolloProvider:
import { ApolloProvider } from '@apollo/client'; import ApolloErrorBoundary from './ApolloErrorBoundary'; import client from './apolloClient'; export default function App() { return ( <ApolloErrorBoundary> <ApolloProvider client={client}> {/* 你的应用页面组件 */} </ApolloProvider> </ApolloErrorBoundary> ); }
3. 封装自定义Hook统一处理错误(局部+全局结合)
如果你想更灵活地控制每个查询/变更的错误处理,同时保持统一逻辑,可以封装自定义的useQuery和useMutation Hook:
import { useQuery as useApolloQuery, useMutation as useApolloMutation } from '@apollo/client'; import { Alert } from 'react-native'; // 封装安全查询Hook export const useSafeQuery = (query, options = {}) => { const { data, loading, error, ...rest } = useApolloQuery(query, options); if (error) { handleApolloError(error); } return { data, loading, error, ...rest }; }; // 封装安全变更Hook export const useSafeMutation = (mutation, options = {}) => { const [mutate, result] = useApolloMutation(mutation, options); const safeMutate = async (...args) => { try { const response = await mutate(...args); return response; } catch (error) { handleApolloError(error); throw error; // 如果需要上层组件继续处理,可以抛出错误 } }; return [safeMutate, result]; }; // 统一的错误处理函数 const handleApolloError = (error) => { console.log('Apollo请求错误:', error); if (error.networkError?.statusCode === 400) { Alert.alert('请求错误', '查询/变更格式不正确,请检查语法'); } else if (error.graphQLErrors) { const errorMsg = error.graphQLErrors[0]?.message || '请求失败'; Alert.alert('查询错误', errorMsg); } };
之后在组件里直接用这些自定义Hook代替原生的:
import { useSafeQuery } from './hooks'; import { GET_USER_QUERY } from './queries'; function UserScreen() { const { data, loading } = useSafeQuery(GET_USER_QUERY); if (loading) return <Text>加载中...</Text>; return <Text>用户名: {data?.user?.name}</Text>; }
总结
优先推荐用**方案1(onError链接)做全局拦截,配合方案2(错误边界)**做兜底,这样绝大多数错误都能被优雅处理。如果需要更灵活的局部控制,再结合方案3的自定义Hook。
内容的提问来源于stack exchange,提问作者Simon




