我正在尝试定义两种类型,它们的外观应类似于:
export type IQuery<P, U> = {
request: string;
params: (props: P, upsteam?: U) => object;
key: (props: P, upstream?: U) => string;
forceRequest: boolean;
depends?: QueryMap
}
export type QueryMap = {
[k: string]: IQuery
};
我要表达的约束是params
和key
的两个参数具有相同的类型,而QueryMap只是从字符串到任意{{1 }}(类型无关紧要)。编译器在这里抱怨是因为它希望为IQuery
指定一个类型,但是重点是映射中的每个IQuery
应该独立地参数化。有什么办法可以用打字稿表达出来?
此外,如果可能的话,我想获得有关IQuery
中存在的上游QueryMap
的形状的信息/保证,当我遍历这棵树时。
答案 0 :(得分:2)
您可以做的最简单的事情是这样的:
export type QueryMap = {
[k: string]: IQuery<any, any>
};
它不是完全类型安全的,但与您要表示的内容相差不远。如果您不想丢失类型为QueryMap
的值的类型信息,请允许编译器推断出较窄的类型,并使用通用辅助函数来确保其为有效的QueryMap
,如下所示:
const asQueryMap = <T extends QueryMap>(t: T) => t;
const queryMap = asQueryMap({
foo: {
request: "a",
params(p: string, u?: number) { return {} },
key(p: string, u?: number) { return "hey" },
forceRequest: true
}
});
即使类型queryMap.foo.params
不是,值string
仍然是接受number
和可选QueryMap['foo']['params']
的方法。
如果您指定了不可分配给QueryMap
的内容,则会出现错误:
const bad = asQueryMap({
foo: {
request: "a",
params(p: string, u?: number) { return {} },
key(p: string, u?: number) { return "hey" },
forceRequest: true
},
bar: {
request: 123,
params(p: number, u?: string) {return {}},
key(p: number, u?: string) {return "nope"},
forceRequest: false
}
}); // error! bar.request is a number
此处显示的不是完全类型安全的问题:
const notExactlySafe = asQueryMap({
baz: {
request: "a",
params(p: number, u?: string) { return {} },
key(p: string, u?: number) { return "hey" },
forceRequest: true
}
});
即使没有P
和U
的合理值在此起作用(当您使用any
时也会发生这种情况),这是可以接受的。如果您需要进一步锁定此值,则可以尝试让TypeScript从值中推断出P
和U
值的集合,或者如果不能,则警告您,但这不是明智的选择。
为了完整起见,这是我的操作方法...使用conditional types通过检查{{来推断P
中每个元素的U
和QueryMap
1}}方法,然后验证params
方法是否与之匹配。
key
现在,以下内容仍然有效:
const asSaferQueryMap = <T extends QueryMap>(
t: T & { [K in keyof T]:
T[K]['params'] extends (p: infer P, u?: infer U) => any ? (
T[K] extends IQuery<P, U> ? T[K] : IQuery<P, U>
) : never
}
): T => t;
这将是一个错误:
const queryMap = asSaferQueryMap({
foo: {
request: "a",
params(p: string, u?: number) { return {} },
key(p: string, u?: number) { return "hey" },
forceRequest: true
}
});
这会稍微提高类型安全性,但会浪费一些const notExactlySafe = asSaferQueryMap({
baz: {
request: "a",
params(p: number, u?: string) { return {} },
key(p: string, u?: number) { return "hey" },
forceRequest: true
}
}); // error, string is not assignable to number
类型的类型比较复杂的类型,因此我不知道这是值得的。 asSaferQueryMap()
可能足以满足大多数目的。
好的,希望能有所帮助;祝你好运!
答案 1 :(得分:0)
您可以使用IQuery<any, any>
。
我不确定您在问题的第二部分想要什么。 TypeScript不会为您提供运行时类型信息。如果您只想在操作单个IQuery
时引用类型变量,则可以将IQuery<any, any>
传递给function myFunction<P, U>(iquery: IQuery<P, U>) { ... }
。
答案 2 :(得分:0)
为了清楚起见,我从您的类型中删除了不相关的信息。解决方案归结为基本上添加了三行代码。
type Check<T> = QueryMap<T extends QueryMap<infer U> ? U : never>
export type IQuery<P, U, TQueryMap extends Check<TQueryMap>> = {
prop1: (param1: P, param2?: U) => number;
prop2: (param1: P, param2?: U) => string;
prop3?: TQueryMap
}
export type QueryMap<T> = {
[K in keyof T]: T[K]
};
// type constructors
const asQueryMap = <T>(x: QueryMap<T>) => x
const asQuery = <P, U, V extends QueryMap<any>>(x: IQuery<P, U, V>) => x
编译器可以正确推断所有类型。
重要:如果(且仅当您)使用type constructors
(见上文)构造结构时,您可以认为自己完全是静态类型安全的。
以下是测试用例:
no compile errors
// Ok -- No compile-time error and correctly infered !
const queryMap = asQueryMap({
a: asQuery({
prop1: (param1: string, param2?: number) => 10,
prop2: (param1: string, param2?: number) => "hello",
}),
b: asQuery({
prop1: (param1: string, param2?: string) => 10,
prop2: (param1: string, param2?: string) => "hello",
}),
c: asQuery({
prop1: (param1: Array<number>, param2?: number) => 10,
prop2: (param1: Array<number>, param2?: number) => "hello",
})
})
const query = asQuery({
prop1: (param1: string, param2?: number) => 10,
prop2: (param1: string, param2?: number) => "hello",
prop3: queryMap
})
Compile-time errors
您会看到下面的一些编译时错误被捕获。
// Ok --> Compile Error: 'prop2' signature is wrong
const queryMap2 = asQueryMap({
a: asQuery({
prop1: (param1: Array<string>, param2?: number) => 10,
prop2: (param1: Array<number>, param2?: number) => "hello",
})
})
// Ok --> Compile Error: 'prop3' is not of type QueryMap<any>
const query2 = asQuery({
prop1: (param1: string, param2?: number) => 10,
prop2: (param1: string, param2?: number) => "hello",
prop3: 10 // <---- Error !
})
谢谢 干杯