繁体   English   中英

如何从 Typescript 中的导入文件推断类型

[英]How to infer types from an imported file in Typescript

我有一个棘手的打字稿问题,我似乎无法解决。 我想根据泛型推断值。 但是,要注意的是推断的类型需要来自导入的文件。

举例说明,这是我的通用函数。

import { DocumentNode, print } from "graphql"

type GraphqlRequest = <Query, Variables = {}>({ query, variables }: { query: DocumentNode, variables?: Variables }) => Promise<Query>

const fetchQuery: GraphqlRequest = async ({ query, variables = {} }) => {
  return database(JSON.stringify({ query: print(query), variables }))
}

我以以下方式使用它:

import { UserBlogItems, UserBlogItemsQuery, UserBlogItemsQueryVariables } from "~/graphql"

fetchQuery<UserBlogItemsQuery, UserBlogItemsQueryVariables>({ query: UserBlogItems, variables: { user } })

这很好用,因为我可以检查变量和查询响应的正确类型。


我想做的只是包含DocumentNode并让 Typescript 从“~/graphql` 文件中查找QueryVariables 。这样我可以让函数看起来更干净。

import { UserBlogItems } from "~/graphql"

fetchQuery({ query: UserBlogItems, variables: { user }})

我可以确保 100% 始终在~/graphql文件中格式为:

  • DocumentNode: [myquery] (例如UserBlogItems )
  • Query: [myquery]Query (例如UserBlogItemsQuery
  • Variables: [myquery]QueryVariables (例如UserBlogItemsQueryVariables

这在打字稿中可能吗?

对于为我们推断这些泛型的函数,我们需要将每个文档节点映射到其各自的查询和查询变量类型。 这是通过这样的元组列表完成的:

type QueryTypeMap = [
    [UserBlogItems, UserBlogItemsQuery, UserBlogItemsQueryVariables],
    [UserInfo, UserInfoQuery, UserInfoQueryVariables],
];

第一个元素是文档节点,其次是查询类型,最后是查询变量。 您也可以这样写并编辑我们稍后访问它们的位置以更明确:

type QueryTypeMap = [
    [UserBlogItems, { query: UserBlogItemsQuery; vars: UserBlogItemsQueryVariables }],
    // ...
];

接下来,我们创建将根据给定文档节点为我们检索类型的类型:

type FindQueryTypes<T> = {
    [K in keyof QueryTypeMap & string]:
        // If this value is a tuple (AKA one of our map entries)
        QueryTypeMap[K] extends ReadonlyArray<unknown>
            // And the document node matches with the document node of this entry
            ? T extends QueryTypeMap[K][0]
                // Then we have found our types
                ? QueryTypeMap[K]
                // Or not
                : never
            // Not even a map entry
            : never;
}[keyof QueryTypeMap & string];

为什么有keyof QueryTypeMap & string QueryTypeMap是一个元组,这意味着keyof QueryTypeMap包含number类型,这会干扰我们试图找到正确查询类型的类型。 所以我们可以通过只允许带有& string的字符串键来排除它。

我们还必须注意, QueryTypeMap的键K是数组的任何键。 例如,它可以是indexOfsplice 这就是为什么我们必须首先检查QueryTypeMap[K]是否是一个元组(这意味着它是我们地图的一个条目,而不是一个内置的数组属性)。

最后,我们在函数类型中使用这种类型:

type GraphQLRequest = <
    D extends typeof DocumentNode,
    Types extends QueryTypeMap[number] = FindQueryTypes<InstanceType<D>>
>(
    { query, variables }: { query: D, variables?: Types[2] }
) => Promise<Types[1]>;

TypeScript 会为我们推断出泛型类型D (任何扩展 DocumentNode 的类构造函数都是 DocumentNode 的typeof DocumentNode ),这意味着我们可以在第二种泛型类型中直接使用它而无需麻烦。

我们将Types限制为QueryTypeMap[number]类型,也就是说它只能是QueryTypeMap的任何条目。 现在我们用上面的类型为这个泛型赋予它的值。

您会看到那里有一个额外的InstanceType ,这是因为D不是UserBlogItemsUserInfo 它实际上是typeof UserBlogItemstypeof UserInfo (构造函数)。 要获取实例类型,我们使用恰当命名的内置类型InstanceType

在那之后,我们坐得很漂亮。 我们有正确的查询类型。 现在我们只使用它们。

对于variables的类型,我们使用Types[2]来获取查询变量的类型。

对于返回类型,我们使用Types[1]来获取查询类型。

再一次, 操场,所以你现在可以自己修补它,因为你已经对它有了一些了解。

而不是通过使用命名约定来推断类型的一些黑暗魔法。 为什么不规范化从 ~/graphql 导出内容的方式。 像这样:

//==== ~/graphql ====

interface UserBlogItems extends DocumentNode{
  foo: string;
} 
type UserBlogItemsQuery = ASTNode & {
  bar: string;
} 
interface UserBlogItemsQueryVariables{
  user:string;
}

export interface UserBlogEntity {
    documentNode:UserBlogItems;
    query: UserBlogItemsQuery;
    variables: UserBlogItemsQueryVariables;
}

//-----------------

并像这样定义 fetchQuery:

interface GraphqlRequestData {
    documentNode:DocumentNode;
    query: ASTNode;
    variables?: Record<string,any>;
}


const fetchQuery = async <T extends GraphqlRequestData>({ query, variables }: {query:T['documentNode'], variables?:T['variables']}):Promise<T['query']> => {
  return database(JSON.stringify({ query: print(query), variables })) as Promise<T['query']>;
};

所以你最终可以像这样使用它:

const userBlogItems :UserBlogItems = {
    foo: `test`
} as UserBlogItems;
const userBlogVars: UserBlogItemsQueryVariables = {
    user: 'user_name'
}


const run = async()=>{
    const returnQuery = await fetchQuery<UserBlogEntity>({ query: userBlogItems, variables: userBlogVars });
    console.log(returnQuery.bar)
}

这是playgorund链接: 游乐场

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM