[英]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` 文件中查找Query
和Variables
。这样我可以让函数看起来更干净。
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
是数组的任何键。 例如,它可以是indexOf
或splice
。 这就是为什么我们必须首先检查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
不是UserBlogItems
或UserInfo
。 它实际上是typeof UserBlogItems
或typeof 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.