【问题标题】:Apollo Client resolver only triggers onceApollo 客户端解析器仅触发一次
【发布时间】: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 } });
      }}
    />
  );
};

【问题讨论】:

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

标签: reactjs react-apollo apollo-client resolver


【解决方案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 的反应级联。

【讨论】:

  • 抱歉耽搁了这么久。非常感谢您的回答,非常有帮助。
猜你喜欢
  • 2020-08-27
  • 2020-07-22
  • 2020-05-03
  • 2019-07-25
  • 2021-01-25
  • 2021-09-25
  • 1970-01-01
  • 2015-05-10
  • 2018-09-30
相关资源
最近更新 更多