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> {
    ...
}

问题有两个:

  1. 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。删除约束保证递归将起作用,并且 ST 键的交集保证编译器将识别 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

我不知道这是为什么,或者它是否是错误、限制或其他原因。但你应该记住这一点,也许吧。或者可能不会,如果功能在发布前发生变化。谁知道呢!

祝你好运!