提问人:kael 提问时间:2/24/2021 最后编辑:kael 更新时间:3/13/2021 访问量:495
Typescript 无法使用定义为 T 类型键子集的键来索引类型 T
Typescript Cannot index type T using key defined as subset of keys of type T
问:
注意:请参阅下面的编辑,了解基于 @GarlefWegart 响应的最终解决方案。
我正在尝试为动态 GraphQL 查询结果编写泛型类型(更多是为了好玩,因为我确信这些可能已经存在于某个地方)。
我非常接近,但发现了一个奇怪的问题。完整的代码在操场上,并在下面复制。
问题在于使用派生对象的键对对象进行索引,这应该有效,但由于某种原因失败了。请注意,在定义中,我不能使用 进行索引,即使它被定义为 的键,并且被定义为 的属性的子集。这意味着 的所有键也必然是 的键,因此使用任何键 进行索引应该是安全的。但是,Typescript 拒绝这样做。Result
T
K
K
U
U
T
U
T
T
U
type SimpleValue = null | string | number | boolean;
type SimpleObject = { [k: string]: SimpleValue | SimpleObject | Array<SimpleValue> | Array<SimpleObject> };
type Projection<T extends SimpleObject> = {
[K in keyof T]?:
T[K] extends SimpleObject
? Projection<T[K]>
: T[K] extends Array<infer A>
? A extends SimpleObject
? Projection<A>
: boolean
: boolean;
};
type Result<T extends SimpleObject, U extends Projection<T>> = {
[K in keyof U]:
U[K] extends false
? never // don't return values for false keys
: U[K] extends true
? T[K] // return the original type for true keys
// ^^vv All references to T[K] throw errors
: T[K] extends Array<infer A>
? Array<Result<A, U[K]>> // Return an array of projection results when the original was an array
: Result<T[K], U[K]>; // Else it's an object, so return the projection result for it
}
type User = {
id: string;
email: string;
approved: string;
address: {
street1: string;
city: string;
state: string;
country: {
code: string;
allowed: boolean;
}
};
docs: Array<{
id: string;
url: string;
approved: boolean;
}>
}
const projection: Projection<User> = {
id: false,
email: true,
address: {
country: {
code: true
}
},
docs: {
id: true,
url: true
}
}
const result: Result<User, typeof projection> = {
email: "[email protected]",
address: {
country: {
code: "US"
}
},
docs: [
{
id: "1",
url: "https://abcde.com/docs/1"
},
{
id: "2",
url: "https://abcde.com/docs/2"
}
]
}
任何见解都是值得赞赏的。
编辑 2021 年 3 月 10 日
我能够根据 Garlef Wegart 在下面的回应找到一个可接受的解决方案。请参阅此处的代码。
但是请注意,它非常挑剔。它非常适合我的用例,因为我正在输入一个 GraphQL API,因此响应通过网络进入,然后被转换为输入参数推断的值,但这些类型在其他情况下可能不起作用。对我来说,重要的部分不是赋值部分,而是结果类型的消耗,这个解决方案确实适用于此。祝其他任何尝试这样做的人好运!unknown
第二点:我还在 github 上将这些类型作为小型 Typescript 包发布(这里)。要使用它,只需将 @kael-shipman:repository=https://npm.pkg.github.com/kael-shipman
添加到您的 npmrc 的某处,然后像往常一样安装包。
答:
从定义来看,这是真实而明显的,它是 -- 的子集。Projection
keyof Projection<T>
keyof T
T
但是:扩展(! 所以它本身可以有 T 中不存在的键。存储在这些键下的值基本上可以是任何东西。U
Projection<T>
U
所以:映射不是正确的做法。相反,您可以映射 .keyof U
keyof T & keyof U
您还应该通过添加 .否则,您可以传入具有错误属性的对象。(我想在你的情况下应该是?U
U extends ... & Record<string, DesiredConstraints>
... & SimpleObject
下面是一个简化的示例(没有域的复杂性),说明了一些复杂性(Playground 链接):
type Subobject<T> = {
[k in keyof T as T[k] extends "pass" ? k : never]:
T[k]
}
type SomeGeneric<T, U extends Subobject<T>> = {
[k in keyof U]:
k extends keyof T
? "yep"
: "nope"
}
type Sub = Subobject<{ a: 1, b: "pass" }>
// This is not what we want: `c: "nope"` shoud not be part of our result!
type NotWanted = SomeGeneric<{ a: 1, b: "pass" }, { b: "pass", c: "other" }>
type Safe<T, U extends Subobject<T>> = {
[k in (keyof U & keyof T)]:
k extends keyof T
? "yep"
: "nope"
}
type Yeah = Safe<{ a: 1, b: "pass" }, { b: "pass", c: "other" }>
// Next problem: U can have bad properties!
function doStuffWithU<T, U extends Subobject<T>>(u: U) {
for (const val of Object.values(u)) {
if (val === "other") {
throw Error('Our code breaks if it receives "other"')
}
}
}
const u = { b: "pass", c: "other" } as const
// This will break but the compiler does not complain!
const error = doStuffWithU<Sub, typeof u>(u)
// But this can be prevented
type SafeAndOnlyAllowedProperties<T, U extends Subobject<T> & Record<string, "pass">> = {
[k in (keyof U & keyof T)]:
// No complaints here due to `& Record<string, "pass">`
OnlyAcceptsPass<U[k]>
}
type OnlyAcceptsPass<V extends "pass"> = "pass!"
// The type checker will now complain `"other" is not assignable to type "pass"`
type HellYeah = SafeAndOnlyAllowedProperties<{ a: 1, b: "pass" }, { b: "pass", c: "other" }>
编辑:再想一想:在定义函数而不是泛型类型时,您也可以使用以下模式来防止错误输入
const safeFn = <U extends Allowed>(u: U & Constraint) => {
// ...
}
评论
U
Projection<T>
Projection<T>
U = Projection<T>
评论
Array<Projection<A>>
Projection