Apollo useMutation 的 refetchQueries 的 TypeScript 类型?

TypeScript type for Apollo useMutation's refetchQueries?

我有一个带有 TypeScript 类型的 Apollo useMutation 挂钩:

const MY_MUTATION = gql`
  mutation($source: String!) {
    doSomething(source: $source) {
      result
    }
  }
`

const [myMutation] = useMutation<ReturnPayloadType, MutationVariables>(MY_MUTATION);

这在我使用它时提供了类型安全:

myMutation({
  variables: {
    source: 'some source',
    // foo: 'bar' This would error if uncommented. 
  }
})

这是有效的,但现在我需要使用 refetchQueries 键。我怎样才能给它添加类型?

myMutation({
  variables: {
    source: 'some source',
    // foo: 'bar' This would error if uncommented. 
  },
  refetchQueries: [
    {
      query: MY_OTHER_QUERY,
      variables: {
        foo: 'bar' // I want to add type safety here
      }
    }
  ]
})

我对 Apollo 不是很熟悉,但是你可以通过添加辅助函数来解决你的问题。在下面的代码中,我将其命名为 typedQuery。很可能 Apollo 已经内置了这样的功能,但同样,我对这个库的了解还不够。

import { gql, QueryFunctionOptions, TypedDocumentNode, useMutation } from '@apollo/react-hooks';

const MY_MUTATION = gql`
  mutation($source: String!) {
    doSomething(source: $source) {
      result
    }
  }
`;
type MyMutationPayloadType = { result: string };
type MyMutationVariables = {
  source: string;
};

const MY_QUERY = gql`
  query($value: Int!) {
    findSomething(value: $value) {
      someProperty
    }
  }
`;
type MyQueryVariables = {
  value: number;
};

function typedQuery<TVariables>(
  options: QueryFunctionOptions<unknown, TVariables> & {
    query: TypedDocumentNode<unknown, TVariables>;
  },
) {
  return options;
}

function MyComponent() {
  const [myMutation] = useMutation<MyMutationPayloadType, MyMutationVariables>(MY_MUTATION);

  myMutation({
    variables: {
      source: 'a',
    },
    refetchQueries: [
      typedQuery<MyQueryVariables>({
        query: MY_QUERY,
        variables: {
          value: 1,
          // value: 'a' <-- gives compiler error
        },
      }),
    ],
  });
}

简答

你真的做不到。从 Apollo Client v3.4.0 开始,输入 refetchQueries 最终解析为:

| "all"
| "active"
|  Array<string | DocumentNode | QueryOptions>`

打字没有类型参数(通用)来让您为 refetchQueries 指定您自己的打字。我会在运行时依赖 GraphQL 的类型保护,并可能在编译时将类型转换作为次要保护。您可以将传递给 refetchQueries 的每个对象的类型转换为 QueryOptions,并通过 QueryOption 的类型参数指定变量的类型。类型转换确实有一个警告,您需要记住使用它,并且它不能保证变量类型和查询匹配。

myMutation({
  variables: {
    source: 'some source',
    // foo: 'bar' This would error if uncommented. 
  },
  refetchQueries: [
    {
      query: MY_OTHER_QUERY,
      variables: {
        foo: 'bar'
      }
    } as QueryOptions<{ foo: string }>
  ]
})

长答案

从 Apollo Client v3.4.0 开始,refetchQueries 选项的类型如下:

  refetchQueries?:
    | ((result: FetchResult<TData>) => InternalRefetchQueriesInclude)
    | InternalRefetchQueriesInclude;

https://github.com/apollographql/apollo-client/blob/release-3.4/src/core/watchQueryOptions.ts#L223-L225

其中 InternalRefetchQueriesInclude 键入为:

export type InternalRefetchQueriesInclude =
  | InternalRefetchQueryDescriptor[]
  | RefetchQueriesIncludeShorthand;

https://github.com/apollographql/apollo-client/blob/release-3.4/src/core/types.ts#L35-L37

然后上面基本上解析为:

| "all"
| "active"
|  Array<string | DocumentNode | QueryOptions>`

https://github.com/apollographql/apollo-client/blob/release-3.4/src/core/types.ts#L26-L29

QueryOptions 本身是 here.

在这些类型中,没有类型参数(通用)允许您为 refetchQueries 指定您自己的类型。尽管即使泛型可用,您也需要解决尝试创建强制“给定具有 类型 的 属性 query 的对象的类型的问题我们需要另一个 属性 variables 和另一个 type”。为什么这很重要?好吧,要回答这个问题,您需要问输入这个有什么意义?如果您使用给定的查询,您希望确保在编译时而不是运行时捕获任何传入的错误类型的变量,并降低用户遇到错误的风险。使用函数很容易,因为函数的标识符和类型的标识符是相同的(它内置于函数中,类型表示“给定此函数,它应该具有 type 的参数") 但在我们的例子中,我们没有函数。因此,这就是为什么我以“你真的不能”开始我的回答。

我确实探索了利用 typeof 的可能性,如下所示:

interface MyQueryNameRefetch extends QueryOptions {
  query: typeof MY_QUERY;
  variables: QueryVariables
}

在我最初的测试中,我只使用了 const 字符串并且错误地将效果推断为上述可能性。进一步的测试表明这不会产生我们想要的效果,因为只有文字类型和枚举成员在使用 const.

声明或断言时不会扩大其类型

最接近的是输入变量:

  refetchQueries: [
    {
      query: MY_OTHER_QUERY,
      variables: {
        foo: 'bar'
      }
    } as QueryOptions<{ foo: string }>
  ]

关于 Arvid 回答的注释

此时我想谈谈 @Arvid's 。有一个功能:

function typedQuery<TVariables>(
  options: QueryFunctionOptions<unknown, TVariables> & {
    query: TypedDocumentNode<unknown, TVariables>;
  },
) {
  return options;
}

你会像这样使用它:

function MyComponent() {
  const [myMutation] = useMutation<MyMutationPayloadType, MyMutationVariables>(MY_MUTATION);

  myMutation({
    variables: {
      source: 'a',
    },
    refetchQueries: [
      typedQuery<MyQueryVariables>({
        query: MY_QUERY,
        variables: {
          value: 1,
          // value: 'a' <-- gives compiler error
        },
      }),
    ],
  });
}

这与转换非常相似,但实际上并不是一个好主意。它具有铸造的缺点(您必须记住使用它);以及其他功能,例如具有运行时效果,因为该函数将存在于编译代码和调用堆栈中(您的类型不应该具有运行时效果)以及对代码理解方式的影响(函数传达了一些单元运行时功能,但此处没有运行时功能。

根本原因再次归结为我之前描述的关于标识符和类型如何断开连接的问题。

您可以使用 *awaitRefetchQueries¨ 等待查询的执行:

awaitRefetchQueries: true,
refetchQueries: [
  {
    query: YOUR_QUERY,
  },
],