Apollo 客户端解析器仅触发一次

Posted

技术标签:

【中文标题】Apollo 客户端解析器仅触发一次【英文标题】:Apollo Client resolver only triggers once 【发布时间】:2020-01-31 18:17:44 【问题描述】:

我目前正在开发一个反应应用程序,它使用 GraphQL 后端并具有额外的本地状态。我正在使用解析器来解析随时间变化的本地字段,但解析器只触发一次。

我尝试使用 cache.readQuery 重新运行查询以防本地字段发生更改,但它似乎没有按我的预期工作。

export const resolvers = 
  Query: 
    resolvedDevice: (obj, args,  cache , info) => 
      const data = cache.readQuery(
        query: gql`
          query 
            selectedDevice @client
          
        `
      );

      // do stuff with the data
    
  ,
  Mutation: 
    selectDevice: (_,  id ,  cache ) => 
      cache.writeData( data:  selectedDevice: id  );
    
  
;

const query = gql`
  query GetResolvedDevice 
    resolvedDevice @client
  
`;

在这种情况下,解析器中的“resolvedDevice”只执行一次,即使我通过突变“selectDevice”对缓存进行了突变。我希望通过突变更改本地状态时,解析器也会再次运行,因为缓存正在更改。

这是执行查询的代码:

const ModalContainer = props => 
  const  loading, error, data  = useQuery(query);

  if (loading || error) 
    return null;
  

  return (
    <Modal
      device=data.resolvedDevice
    />
  );
;

在这个组件中,我在 selectedDevice 上运行一个突变:

export const SELECT_DEVICE = gql`
  mutation SelectDevice($id: String!) 
    selectDevice(id: $id) @client
  
`;

const DevicesNoGeoContainer = () => 
  const [selectDevice] = useMutation(SELECT_DEVICE);

  return (
    <DevicesNoGeo
      onGeoClick=id => 
        selectDevice( variables:  id  );
      
    />
  );
;

【问题讨论】:

查询不会自行自动更新。您可以在运行查询/突变的地方发布代码吗?需要明确的是,所涉及的查询都是本地的,还是您从后端获取数据? 这有帮助吗? 【参考方案1】:

当缓存中的值发生变化时,Apollo 知道更新从缓存中提取字段的观察查询。在这种情况下,查询中的字段由本地解析器代替。这意味着 Apollo 没有缓存条目来订阅和响应该特定查询。因此,第一个查询已完成,除非您在挂钩结果中使用 refetch 显式触发它,否则您将不会获得任何查询更新。

我们希望解决此问题的一种方法是在缓存中“持久化”派生字段并在组件查询中使用缓存实现字段。我们可以通过显式观察源字段 (selectedDevice) 并在处理程序中将派生字段 (resolvedDevice) 写回缓存来做到这一点(我将继续使用您的字段名称,尽管您可能会考虑重命名它如果你走这条路线,因为它似乎是以它的定义方式命名的)。

概念验证

export const resolvers = 
  Mutation: 
    selectDevice: (_,  id ,  cache ) => 
      cache.writeData( data:  selectedDevice: id  );
    
  
;

const client = new ApolloClient(
  resolvers
);

const sourceQuery = gql`
  query 
    selectedDevice @client
  `;

// watch the source field query and write resolvedDevice back to the cache at top-level
client.watchQuery( query: sourceQuery ).subscribe(value =>
  client.writeData( data:  resolvedDevice: doStuffWithTheData(value.data.selectedDevice)  );

const query = gql`
  query GetResolvedDevice 
    resolvedDevice @client
  
`;

因为传递给watchQuery 的查询中的字段位于缓存中,所以每次更改都会调用您的处理程序,并且我们会将派生字段写入缓存作为响应。 而且因为resolvedDevice 现在存在于缓存中,所以查询它的组件现在将在它发生变化时获得更新(即每当“upteam”selectedDevice 字段发生变化时)。

现在您可能不想真正将源字段监视查询放在顶层,因为无论您是否使用渲染组件,它都会在您的应用程序启动时运行并监视。如果您对一堆本地状态字段采用这种方法,这将特别糟糕。 我们正在开发一种方法,您可以在其中以声明方式定义派生字段及其实现功能:

export const derivedFields: 
  resolvedDevice: 
    fulfill: () => client.watchQuery( query: sourceQuery ).subscribe(value =>
      client.writeData( data:  resolvedDevice: doStuffWithTheData(value.data.selectedDevice),
  
;

然后使用 HOC 让他们参与进来:

import  derivedFields  from './the-file-above';

export const withResolvedField = field => RenderingComponent => 
  return class ResolvedFieldWatcher extends Component 
    componentDidMount() 
      this.subscription = derivedFields[field].fulfill();
    
    componentDidUnmount() 
      // I don't think this is actually how you unsubscribe, but there's
      // some way to do it
      this.subscription.cancel();
    
    render() 
      return (
        <RenderingComponent ...this.props  />
      );
    
  ;
;

最后包装你的模态容器:

export default withDerivedField('resolvedDevice')(ModalContainer);

请注意,我在这里的结尾是非常假设的,我只是输入它而不是拉下我们的实际代码。我们也回到了 Apollo 2.5 和 React 2.6,所以你可能不得不调整钩子等方法。但原则应该是相同的:通过查看缓存中源字段的查询来定义派生字段 em>,并将派生字段写回缓存。然后你有一个从源数据到基于派生字段的组件渲染 UI 的反应级联。

【讨论】:

抱歉耽搁了这么久。非常感谢您的回答,非常有帮助。

以上是关于Apollo 客户端解析器仅触发一次的主要内容,如果未能解决你的问题,请参考以下文章

Apollo 2.x:订阅解析器未触发?

Apollo Server 解析大数据时性能缓慢

Apollo 订阅解析器永远不会激活?

Apollo GitHunt-React:updateCommentsQuery?

在客户端解析器中导入类型时,如何避免使用 Apollo 客户端和“graphql-codegen”的角度项目中的循环依赖?

使用 TypeGraphQL 在浏览器中为 Apollo 本地状态生成 typedef 和解析器