Typescript 的条件类型 Request/Response
Conditional Types for Typescript Request/Response
我目前正在研究 typescript@next
,它现在合并了 Conditional Types PR。
我试图做的是创建一种方法,该方法对数据模型类型进行深度记录,returns 是一种基于记录中字段的深度选择。例如,我希望能够执行以下操作:
loadUser({
id: true,
name: {
first: true
}
});
当 User
类型看起来像
type User = {
name: {
first: string;
last: string;
};
id: string;
}
在这种情况下, 和 loadUser
的 return 值将匹配
{
id: string;
name: {
first: string;
}
}
到目前为止我所做的如下:
type DeepSelect<T> = {[K in keyof T]?: T[K] extends string ? boolean : DeepSelect<T[K]>};
type DeepPick<T, S extends DeepSelect<T>> = {[K in keyof S]: S[K] extends DeepSelect<infer U> ? DeepPick<T[K], U> : T[K]}
function loadUser<S extends DeepSelect<User>>(select: S): DeepPick<User, S> {
...
}
问题有两个:
- 在
DeepPick
定义中使用 T[K]
会导致 type K cannot be used to index type T
错误。
我觉得鉴于 DeepSelect
的定义,其中所有键都来自通用 T
中的键,T[K]
在这里是完全有效的,因为 [=26= 中的任何键] 也将成为 T
.
中的键
2。在 DeepPick
定义中最后一次使用 S[K]
错误说 type boolean | DeepSelect<T[K]> is not assignable to type DeepSelect<T[K]>
而在这里,我觉得因为这部分类型条件只有在 S[K]
不扩展布尔值时才会被命中,那么它应该能够推断出 S[K]
不是 boolean | DeepSelect<T[K]>
而只是 DeepSelect<T[K]>
我意识到由于这个 PR 是昨天才合并的,所以没有多少人会对这些问题有深入的了解,但如果有人能帮助我理解如何正确构造这些类型,我将非常感激。
更新 1
好的,我想我已经使用新的 Type Inference 解决了问题 #2。我已经更改了 DeepPick
类型的定义:
type DeepPick<T, S extends DeepSelect<T>> = {[K in keyof S]: S[K] extends boolean ? T[K] : DeepPick<T[K], S[K]>}
至:
type DeepPick<T, S extends DeepSelect<T>> = {[K in keyof S]: S[K] extends DeepSelect<infer U> ? DeepPick<T[K], U> : T[K]}
这确实是最前沿的东西,所以我不知道我给出的建议是否会在下一个版本中 相关,更不用说 好。也就是说,我有这样的东西:
type DeepSelect<T> = {
[K in keyof T]? : T[K] extends object ? DeepSelect<T[K]> : true
};
type DeepPick<T, S> = {
[K in keyof S & keyof T]: S[K] extends true ? T[K] : DeepPick<T[K], S[K]>
}
我已经将它们都更改为仅处理 true
而不是 boolean
,因为您似乎并不真的打算 false
发出 [=58] 的信号=] 应该包括在内(你呢?)。
如果 属性 是 object
,我还更改了 DeepSelect
以向下递归,所以如果 User
有一些非 string
属性(例如 age: number
?)。这使它更通用一些。
最后,我删除了对 DeepPick
的约束,即 S
需要扩展 DeepSelect<T>
,而不是映射到 keyof S
,我映射到 keyof S & keyof T
。删除约束保证递归将起作用,并且 S
和 T
键的交集保证编译器将识别 T[K]
和 S[K]
都存在。编译器可能在一个完美的世界中意识到你编写它的方式是有效的,但我想这个世界并不完美。
请注意,函数 loadUser
仍然对 S
有约束:
declare function loadUser<S extends DeepSelect<User>>(
select: S
): DeepPick<User, S>;
所以它会按预期工作:
const u = loadUser({
id: true,
name: {
first: true
}
});
u.id
u.name.first
请注意,在您和我的代码中,传递给 loadUser()
的对象上似乎没有 excess property checks。例如:
const u = loadUser({
ib: true, // no error, oops
name: {
first: true
}
});
u.id // error
u.name.first
我不知道这是为什么,或者它是否是错误、限制或其他原因。但你应该记住这一点,也许吧。或者可能不会,如果功能在发布前发生变化。谁知道呢!
祝你好运!
我目前正在研究 typescript@next
,它现在合并了 Conditional Types PR。
我试图做的是创建一种方法,该方法对数据模型类型进行深度记录,returns 是一种基于记录中字段的深度选择。例如,我希望能够执行以下操作:
loadUser({
id: true,
name: {
first: true
}
});
当 User
类型看起来像
type User = {
name: {
first: string;
last: string;
};
id: string;
}
在这种情况下, 和 loadUser
的 return 值将匹配
{
id: string;
name: {
first: string;
}
}
到目前为止我所做的如下:
type DeepSelect<T> = {[K in keyof T]?: T[K] extends string ? boolean : DeepSelect<T[K]>};
type DeepPick<T, S extends DeepSelect<T>> = {[K in keyof S]: S[K] extends DeepSelect<infer U> ? DeepPick<T[K], U> : T[K]}
function loadUser<S extends DeepSelect<User>>(select: S): DeepPick<User, S> {
...
}
问题有两个:
- 在
DeepPick
定义中使用T[K]
会导致type K cannot be used to index type T
错误。
我觉得鉴于 DeepSelect
的定义,其中所有键都来自通用 T
中的键,T[K]
在这里是完全有效的,因为 [=26= 中的任何键] 也将成为 T
.
2。在 DeepPick
定义中最后一次使用 S[K]
错误说 type boolean | DeepSelect<T[K]> is not assignable to type DeepSelect<T[K]>
而在这里,我觉得因为这部分类型条件只有在 S[K]
不扩展布尔值时才会被命中,那么它应该能够推断出 S[K]
不是 boolean | DeepSelect<T[K]>
而只是 DeepSelect<T[K]>
我意识到由于这个 PR 是昨天才合并的,所以没有多少人会对这些问题有深入的了解,但如果有人能帮助我理解如何正确构造这些类型,我将非常感激。
更新 1
好的,我想我已经使用新的 Type Inference 解决了问题 #2。我已经更改了 DeepPick
类型的定义:
type DeepPick<T, S extends DeepSelect<T>> = {[K in keyof S]: S[K] extends boolean ? T[K] : DeepPick<T[K], S[K]>}
至:
type DeepPick<T, S extends DeepSelect<T>> = {[K in keyof S]: S[K] extends DeepSelect<infer U> ? DeepPick<T[K], U> : T[K]}
这确实是最前沿的东西,所以我不知道我给出的建议是否会在下一个版本中 相关,更不用说 好。也就是说,我有这样的东西:
type DeepSelect<T> = {
[K in keyof T]? : T[K] extends object ? DeepSelect<T[K]> : true
};
type DeepPick<T, S> = {
[K in keyof S & keyof T]: S[K] extends true ? T[K] : DeepPick<T[K], S[K]>
}
我已经将它们都更改为仅处理 true
而不是 boolean
,因为您似乎并不真的打算 false
发出 [=58] 的信号=] 应该包括在内(你呢?)。
如果 属性 是 object
,我还更改了 DeepSelect
以向下递归,所以如果 User
有一些非 string
属性(例如 age: number
?)。这使它更通用一些。
最后,我删除了对 DeepPick
的约束,即 S
需要扩展 DeepSelect<T>
,而不是映射到 keyof S
,我映射到 keyof S & keyof T
。删除约束保证递归将起作用,并且 S
和 T
键的交集保证编译器将识别 T[K]
和 S[K]
都存在。编译器可能在一个完美的世界中意识到你编写它的方式是有效的,但我想这个世界并不完美。
请注意,函数 loadUser
仍然对 S
有约束:
declare function loadUser<S extends DeepSelect<User>>(
select: S
): DeepPick<User, S>;
所以它会按预期工作:
const u = loadUser({
id: true,
name: {
first: true
}
});
u.id
u.name.first
请注意,在您和我的代码中,传递给 loadUser()
的对象上似乎没有 excess property checks。例如:
const u = loadUser({
ib: true, // no error, oops
name: {
first: true
}
});
u.id // error
u.name.first
我不知道这是为什么,或者它是否是错误、限制或其他原因。但你应该记住这一点,也许吧。或者可能不会,如果功能在发布前发生变化。谁知道呢!
祝你好运!