【问题标题】:Best way to extract all the props from a types whose props are conditional types in a generic function with a specified type从具有指定类型的通用函数中的道具是条件类型的类型中提取所有道具的最佳方法
【发布时间】:2020-04-22 19:43:48
【问题描述】:

我有一个提取类型叫做

type ExtractProps<T, K> = { [C in keyof T]:T[C] extends K ? K: never }

我想从中提取一个类型。

type FormRelation<T> = {
  [C in keyof T]: T[C] extends string ? FormField : FormFieldGroup
}

参数也是FormRelation

这种类型应该只从 T 中提取指定的 K 类型。它确实提取了它,但是这种类型在一个应该可以工作的参数中失败了。

FormRelation<T>[K] extends FormField ? FormField : never is not assignable to type T[K] extends string ? FormField : FormFieldGroup

never 在打字稿中被认为是底部类型,因此它不应该包含任何值。这意味着编译器应该忽略 never 类型的道具,因为它不包含值。但是,事实并非如此,这意味着这必须是预期的功能。从具有指定类型的对象中提取属性有哪些解决方法?

新更新

所以我上网查了一下,找到了解决办法

type ExtractProps<T, TProps extends T[keyof T]> = Pick<T, ExtractPropsKey<T, TProps>>;

type ExtractPropsKey<T, TProps extends T[keyof T]> = {
  [P in keyof T]: T[P] extends TProps ? P : never;
}[keyof T];

这些类型有效,但是我的运行时函数不接受 class FormField,因为它不扩展 T[keyof T],即使 FormField 是 FormRelation 道具的可能类型。

有没有办法将所有泛型条件类型提取到一个联合类型中??

Playground Link

【问题讨论】:

  • 你应该发布一个完整的、最好是独立的例子来说明你遇到的问题..否则这太模糊了
  • 我刚刚添加了有关我尝试从中提取的类型和参数类型的更多详细信息。

标签: typescript templates generics metaprogramming typescript-generics


【解决方案1】:

所以我只是求助于重载提取函数来接收预期的返回类型。

declare function extract<T>(object: {[x: string]: any}, propTypes: {new(...values: any): any}): T

如果没有指定泛型,我很确定编译器无法提取特定的条件类型。我仍然会保留以前的泛型类型,因为它在任何没有条件类型以及指定和未指定类型参数的类型上都可以正常工作。

这不是一个完美的解决方案,所以如果有人有更好的解决方案,请随时将其放下。

【讨论】:

    【解决方案2】:

    “通用条件类型”这句话让我害怕。如果条件类型依赖于未指定的泛型类型参数,例如在 run() 的实现中,TypeScript 编译器无法对其进行太多类型分析。它主要延迟分析,并且无法确认任何值都可以分配给它。

    如果指定了类型参数,编译器可以完全评估条件类型并理解,例如,ExtractProps&lt;{a: string, b: number}, number&gt;{b: number}。但是 ExtractProps&lt;T, number&gt; 对于未指定的通用 T 是一个谜。编译器不理解您可以将该类型的任何属性分配给number 类型的变量。

    我倾向于认为泛型条件类型只对那些可以指定泛型类型参数的人有用。对于像run() 这样的通用函数,这意味着它只对该函数的调用者 有用。实现应避免此类类型。让函数实现使用与调用者不同的类型签名的一种方法是将其设为overloaded 函数,如下所示:

    function run<T>(props: FormRelation<T>): void;
    function run(props: Record<string, FormField | FormFieldGroup>) {
      const fields = extract(props, [FormField]);
    }
    

    在这里,run() 的调用者可以指定泛型T,而实现只是将props 视为Record&lt;string, FormField | FormFieldgroup&gt;,这是一种更广泛的具体类型。然后fields 将正常工作。或者更确切地说,它将在没有警告的情况下编译。但是对于extract() 的定义,该类型对您毫无用处。如果你只打算在run() 中使用extract(),那么我建议给它一个这样的签名,而不是你现在的签名:

    declare function extract<V>(
      object: object,
      propsTypes: { new(...value: any): V }[]
    ): Record<string, V>
    

    这会产生已知具有V 值作为其属性的东西。是的,它会忘记object 的特定键,但在run() 的实现中你不可能知道这些,所以让extract() 尝试维护它们是没有意义的。 run() 的实现唯一应该关心的是 extract(props, [FormField]) 应该是属性都是 FormField 的东西,这就是 extract() 所做的。


    所以这就是我建议去的地方。我将在此处提到您的示例代码还有其他问题,尽管它们希望不适用于您的完整代码库:

    • 您的 FormFieldFormFieldGroup 类在结构上是相同的,因为它们都是空的。 That's a bad idea, even for example code。假设您希望编译器看到 FormFieldFormFieldGroup 类型不同,您应该给它们不同的结构。

    • 函数签名function run&lt;T&gt;(props: FormRelation&lt;T&gt;): void; 不是特别有用。如果编译器可以推断类型参数而不是要求用户手动指定它,那么泛型函数通常对用户最友好。但是在这个签名中,无法从props 的类型推断出TFormRelation&lt;T&gt; 有损; props 的类型必须看起来像 {a: FormField, b: FormFieldGroup, c: FormField} 或其他东西,并且没有办法将其逆向工程为像 {a: string, b: number[], c: boolean} 这样的类型。因此,使Tprops 相关的唯一方法是调用者手动指定T。出于什么目的?返回类型为void,不依赖于T。所以调用者几乎没有动力去确保T 被正确使用。从调用者的角度来看,签名run&lt;T extends Record&lt;keyof T, FormField | FormGroup&gt;&gt;(props: T): void; 可能更好。


    好的,希望对您有所帮助。祝你好运!

    Playground link to code

    【讨论】:

    • 好的,但问题是是否有办法将所有可能的条件类型提取到单个联合类型中。
    • 对不起;我的回答解决了问题“我的运行时函数不接受类FormField,因为它不扩展T[keyof T],即使FormFieldFormRelation Props. 的可能类型”恐怕我不不明白“将所有泛型条件类型提取为单个联合类型”的确切含义。也许您可以更新您的示例以明确显示您要转换为什么?我希望从FormRelation&lt;T&gt; 更改为Record&lt;string, FormField | FormFieldGroup&gt; 可能是您前进的正确道路;如果没有,我需要更多细节。再次祝你好运!
    猜你喜欢
    • 1970-01-01
    • 2018-10-09
    • 2019-05-04
    • 1970-01-01
    • 2019-12-09
    • 2021-06-29
    • 2021-08-04
    • 1970-01-01
    • 2021-07-27
    相关资源
    最近更新 更多