【问题标题】:Write a function that uses lookup types on generics编写一个在泛型上使用查找类型的函数
【发布时间】:2020-01-31 13:37:23
【问题描述】:

我想写一个做三件事的函数:

  • 对泛型类型 T 进行操作
  • 接受一个键:K of T 其中 T[K] 必须是布尔值
  • 为 T[K] 赋值

我正在关注this guide,这暗示了这种可能性:

type FunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T];
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;

type NonFunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T];
type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;

interface Part {
    id: number;
    name: string;
    subparts: Part[];
    updatePart(newName: string): void;
}

type T40 = FunctionPropertyNames<Part>;  // "updatePart"
type T41 = NonFunctionPropertyNames<Part>;  // "id" | "name" | "subparts"
type T42 = FunctionProperties<Part>;  // { updatePart(newName: string): void }
type T43 = NonFunctionProperties<Part>;  // { id: number, name: string, subparts: Part[] }

但是,如果我的函数在具体类型上运行,我似乎只能实现我的目标。为了计算 T 的布尔属性名称,我修改了示例中的第一行,如下所示:

type BoolPropNames<T> = { [K in keyof T]: T[K] extends boolean ? K : never }[keyof T];

如果我使用具体类型,它会起作用:

class Thing {
  isGreat: boolean;
}

function assignToThing(thing: Thing, key: BoolPropNames<Thing>) {
  thing[key] = false;
}

但如果我尝试对泛型进行操作,它不会:

function assign<T>(thing: T, key: BoolPropNames<T>) {
  thing[key] = false;
}

当我尝试这样做时,TypeScript 给了我以下错误:

(parameter) key: { [K in keyof T]: T[K] extends boolean ? K : never; }[keyof T]
Type 'false' is not assignable to type 'T[{ [K in keyof T]: T[K] extends boolean ? K : never; }[keyof T]]'.ts(2322)

为什么 TypeScript 不允许我以这种方式对泛型进行操作,我可以做些什么来解决它?

【问题讨论】:

    标签: typescript generics conditional-types


    【解决方案1】:

    TS 无法计算 BoolPropNames&lt;T&gt; 的结果,因为它是在给定 T 时计算的,这与前面没有类型变量但有一些特定类型的示例不同。例如BoolPropNames&lt;Thing&gt; 被计算为isGreatthing[key] 被称为boolean

    BoolPropNames&lt;T&gt; 之前无法计算,因此它不知道结果将是给我们boolean 的键。如果它不知道 think[key] 是布尔值,那么你就不能将其分配给布尔值。

    为了解决这个问题,我们可以例如应用将被视为T[K] 类型的参数。然后 TS 不需要计算类型,因为它始终是给定键的类型值。考虑以下代码:

    function assign<T, K extends BoolPropNames<T>>(thing: T, key: K, value: T[K]) {
      thing[key] = value;
    }
    assign({ a: true }, 'a', false);
    

    如果第三个参数不满足您,并且您只想设置任何值而不将其作为参数,我们也可以进行一些部分应用以创建此类行为。考虑:

    // function which creates assign function
    const createAssign = <T, K extends BoolPropNames<T> = BoolPropNames<T>>
    (value: T[K]) => (thing: T, key: K) => {
      thing[key] = value;
    }
    // below assign function is created for type `Example`
    type Example = { a: boolean };
    const assign = createAssign<Example>(false); 
    assign({a: true}, 'a');
    

    通过部分应用,我们创建了assign 函数,它的工作方式与您原来的工作方式相同,因此它只将false 分配给boolean 字段。通过这种方式,您可以制作所有这些功能。

    当然缺点是我们需要通过设置泛型类型变量来为每种类型生成这样的,所以它不像具有不同参数顺序的解决方案那样多态。

    【讨论】:

    • 但它确实知道密钥会给我们布尔值:T[K] extends boolean ? K : never
    • 在给定类型变量之前,它甚至不会计算它。所以它不在这个级别分析这个类型的定义。所以它不知道
    • 要理解的核心是将 BoolPropNames 视为一个没有输出注释的函数。这意味着直到我们执行它,直到我们不知道它会返回什么。 TS 没有计算它,它在您看到的错误中也可见,TS 只是为您提供整个定义,因为它不知道最终会是什么
    • 感谢您的建议。尽管在我看来 TS 应该假设它适用于任何具有布尔属性的 T !它不应该抱怨,除非我实际上用 T 和 K 调用 assign ,其中 T[K] 不是布尔值。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-01-01
    • 2019-02-01
    • 1970-01-01
    • 2021-10-31
    • 1970-01-01
    • 2020-07-12
    • 1970-01-01
    相关资源
    最近更新 更多