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

时间:2019-10-02 12:57:51

标签: reactjs react-apollo apollo-client resolver

我目前正在使用ReactGraph应用程序,该应用程序使用GraphQL后端并具有其他本地状态。我正在使用解析器来解析随时间变化的本地字段,但是该解析器仅触发一次。

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

$('body.page')

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

以下是执行查询的代码:

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
  }
`;

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

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

  if (loading || error) {
    return null;
  }

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

1 个答案:

答案 0 :(得分:2)

当高速缓存中的值更改时,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现在位于缓存中,因此查询它的组件现在将在其更改时(无论何时“ 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进行反应式级联。