useSubscription无法正常工作,原subscribeToMore方案可正常运行,求助原因
之前用subscribeToMore搭配updateQuery能正常更新数据,换成useSubscription+onSubscriptionData后失效,通常是两者工作机制的核心差异导致的,以下是具体原因及解决思路:
1. 未手动更新Apollo缓存
subscribeToMore的updateQuery是直接修改Apollo缓存的逻辑:它接收当前缓存数据(prev),返回新的缓存状态,Apollo会自动同步这个更新到UI。而useSubscription的onSubscriptionData仅接收订阅推送的数据,不会自动修改缓存。如果你的UI是通过useQuery从缓存读取数据,不手动更新缓存的话,UI完全不会感知到订阅数据的变化。
解决方法:在onSubscriptionData里用client.writeQuery或client.cache.modify手动更新缓存,逻辑和原来的updateQuery一致:
import { useSubscription, useApolloClient } from '@apollo/client'; // 组件内部 const client = useApolloClient(); useSubscription(gqlSubscription, { variables: { input: executeParams(props) }, shouldResubscribe: true, onSubscriptionData: ({ subscriptionData }) => { // 读取当前缓存的旧数据 const prevData = client.readQuery({ query: YOUR_GET_DATA_QUERY, variables: { /* 匹配查询使用的变量 */ } }); // 合并旧数据和新订阅数据,和原来updateQuery的逻辑一致 const newData = { ...prevData, targetList: [...prevData.targetList, subscriptionData.data.newItem] }; // 写入缓存 client.writeQuery({ query: YOUR_GET_DATA_QUERY, variables: { /* 匹配查询使用的变量 */ }, data: newData }); } });
2. 变量动态更新逻辑异常
虽然设置了shouldResubscribe: true,但如果executeParams(props)每次渲染都返回新的对象引用,可能导致订阅频繁重连,甚至变量计算错误。另外,useSubscription的变量更新依赖组件渲染时的计算,如果props变化但变量没有正确同步,订阅会使用旧变量,自然拿不到正确的数据。
解决方法:用useMemo包裹executeProps的结果,确保变量引用稳定:
import { useMemo } from 'react'; // 组件内部 const subscriptionVars = useMemo(() => ({ input: executeParams(props) }), [props]); // 依赖props变化时才重新计算 useSubscription(gqlSubscription, { variables: subscriptionVars, shouldResubscribe: true, // ...其他配置 });
3. 缺失错误处理
subscribeToMore会默认绑定到父Query的错误处理逻辑,而useSubscription是独立订阅,不设置onError的话,订阅失败(比如权限问题、服务端错误)时不会有任何提示,你可能误以为订阅没触发。
解决方法:添加onError回调排查问题:
useSubscription(gqlSubscription, { // ...其他配置 onError: (error) => { console.error('订阅失败:', error.message); console.error('错误详情:', error); } });
4. 订阅与查询的生命周期不匹配
subscribeToMore是依附于某个useQuery/useLazyQuery的订阅,会和查询的生命周期绑定(比如查询卸载时自动取消订阅)。而useSubscription是独立的,若组件重新渲染或卸载,订阅会重新创建/取消。如果你的订阅需要和某个查询的状态(比如查询变量)保持一致,可能出现变量不匹配的问题。
解决方法:确保订阅变量和关联查询的变量完全一致,必要时把订阅和查询放在同一个组件内,或者通过状态同步变量。
内容的提问来源于stack exchange,提问作者user22034785

