Apollo GraphQL React Client订阅异常:仅发送connection_init未完成订阅
GraphQL订阅未发送subscribe消息的修复方案
问题场景
尝试用GraphQL Apollo Client实现订阅Demo,已有可用的GraphQL API,但WebSocket连接仅发送connection_init消息,未触发subscribe,订阅无法正常工作。现有代码如下:
import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import { ApolloClient, InMemoryCache, ApolloProvider, gql, useQuery } from '@apollo/client'; import { split, HttpLink } from '@apollo/client'; import { getMainDefinition } from '@apollo/client/utilities'; import { GraphQLWsLink } from '@apollo/client/link/subscriptions'; import { createClient } from 'graphql-ws'; import { useSubscription } from '@apollo/react-hooks' import reportWebVitals from './reportWebVitals'; const httpLink = new HttpLink({ uri: 'https://mygraphql.api' }); const wsLink = new GraphQLWsLink(createClient({ url: 'wss://mygraphql.api', options: { reconnect: true } })); const splitLink = split( ({ query }) => { const definition = getMainDefinition(query); return ( definition.kind === 'OperationDefinition' && definition.operation === 'subscription' ); }, wsLink, httpLink, ); const client = new ApolloClient({ link: splitLink, cache: new InMemoryCache(), fetchOptions: { mode: 'no-cors', } }); const FAMILIES_SUBSCRIPTION = gql` subscription{ onFamilyCreated { id name } } `; function LastFamily() { const { loading, error, data } = useSubscription(FAMILIES_SUBSCRIPTION, { variables: { }, onData: data => console.log('new data', data) }); if (loading) return <div>Loading...</div>; if (error) return <div>Error!</div>; console.log(data); const family = data.onFamilyCreated[0]; return ( <div> <h1>{family.name}</h1> </div> ); } const root = ReactDOM.createRoot(document.getElementById('root')); root.render( (<ApolloProvider client={client}> <div> <LastFamily /> </div> </ApolloProvider>)); reportWebVitals();
问题原因及修复步骤
1. 修正useSubscription的导入路径
Apollo Client 3.x及以上版本,useSubscription钩子已经整合到@apollo/client包中,无需再从@apollo/react-hooks导入,混用会导致版本兼容问题,直接影响订阅触发逻辑。
修改导入:
// 删除旧导入 import { useSubscription } from '@apollo/react-hooks' // 改为从@apollo/client统一导入 import { ApolloClient, InMemoryCache, ApolloProvider, gql, useQuery, useSubscription } from '@apollo/client';
2. 调整WebSocket客户端配置结构
graphql-ws的createClient参数中,reconnect是顶层配置项,不需要嵌套在options里,旧写法会导致配置不生效,影响连接稳定性。
修改wsLink配置:
const wsLink = new GraphQLWsLink(createClient({ url: 'wss://mygraphql.api', reconnect: true // 直接放在顶层,移除options嵌套 }));
3. 修正订阅数据的处理逻辑
订阅onFamilyCreated返回的是单个新创建的Family对象,而非数组,取data.onFamilyCreated[0]会直接报错。同时,订阅是持续接收数据的,初始loading状态过后,新数据会触发组件重渲染,需要添加兜底处理避免空值报错。
修改LastFamily组件:
function LastFamily() { const { loading, error, data } = useSubscription(FAMILIES_SUBSCRIPTION, { onData: ({ data: newData }) => console.log('new data', newData) }); if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; // 订阅未收到数据时的兜底展示 if (!data?.onFamilyCreated) return <div>No family created yet</div>; const family = data.onFamilyCreated; return ( <div> <h1>Latest Family: {family.name}</h1> <p>ID: {family.id}</p> </div> ); }
4. 移除no-cors配置
fetchOptions.mode: 'no-cors'会限制浏览器处理跨域请求的能力,WebSocket连接可能因此无法正常完成握手,除非API明确要求该模式,否则建议直接移除。
修改ApolloClient配置:
const client = new ApolloClient({ link: splitLink, cache: new InMemoryCache(), // 移除fetchOptions配置 });
修改后的完整代码
import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import { ApolloClient, InMemoryCache, ApolloProvider, gql, useQuery, useSubscription } from '@apollo/client'; import { split, HttpLink } from '@apollo/client'; import { getMainDefinition } from '@apollo/client/utilities'; import { GraphQLWsLink } from '@apollo/client/link/subscriptions'; import { createClient } from 'graphql-ws'; import reportWebVitals from './reportWebVitals'; const httpLink = new HttpLink({ uri: 'https://mygraphql.api' }); const wsLink = new GraphQLWsLink(createClient({ url: 'wss://mygraphql.api', reconnect: true })); const splitLink = split( ({ query }) => { const definition = getMainDefinition(query); return ( definition.kind === 'OperationDefinition' && definition.operation === 'subscription' ); }, wsLink, httpLink, ); const client = new ApolloClient({ link: splitLink, cache: new InMemoryCache(), }); const FAMILIES_SUBSCRIPTION = gql` subscription{ onFamilyCreated { id name } } `; function LastFamily() { const { loading, error, data } = useSubscription(FAMILIES_SUBSCRIPTION, { onData: ({ data: newData }) => console.log('new data', newData) }); if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; if (!data?.onFamilyCreated) return <div>No family created yet</div>; const family = data.onFamilyCreated; return ( <div> <h1>Latest Family: {family.name}</h1> <p>ID: {family.id}</p> </div> ); } const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <ApolloProvider client={client}> <div> <LastFamily /> </div> </ApolloProvider> ); reportWebVitals();
完成以上修改后,WebSocket连接会正常发送connection_init和subscribe消息,订阅成功后就能实时接收新创建的Family数据并渲染到页面上。
内容的提问来源于stack exchange,提问作者shinjidev
相关产品推荐
相关产品推荐

