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

Apollo Client resolver only triggers once

我目前正在开发一个 React 应用程序,它使用 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" 改变了缓存。我预计当通过突变更改本地状态时,解析器也会再次 运行s,因为缓存正在更改。

这是执行查询的代码:

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 } });
      }}
    />
  );
};

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

我们寻求解决此问题的一种方法是 "persisting" 缓存中的派生字段并在组件查询中使用缓存实现的字段。我们可以通过显式监视源字段 (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 现在存在于缓存中,查询它的组件现在将在它发生变化时获得更新(每当 "upsteam" 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,因此您可能必须调整挂钩等方法。原则应该是相同的:通过观察缓存中源字段 上的查询来定义派生字段,并将派生字段写回缓存。然后你有一个反应级联从你的源数据到基于派生字段的组件渲染ui。