如何将keyof用于特定类型的属性?

时间:2018-06-14 06:51:06

标签: typescript

我正在尝试编写一个泛型函数,它可以通过键名在任何对象中切换布尔属性。我读了release notes for TypeScript-2.8并认为条件类型应该解决这类问题。但是,我无法弄清楚如何编写我的函数。

我的函数接受要修改的对象和要修改的键的名称。为了确保只传入布尔属性的键,我使用了条件类型表达式T[K] extends boolean ? K : never。据我了解,如果我尝试传递一个非布尔属性的键,这会导致错误,因为T[K]不满足extends boolean。但是如果我试图传递一个布尔值的键,那么它应该接受K

但是,即使使用此条件,TypeScript也似乎没有在T[K] extends boolean必须为true的函数中实现。所以我无法将从对象读取的值分配回对象。这导致下面显示的第一个错误。第二个错误是类型推断似乎不适用于我的函数。在下面的调用中,到目前为止只有第二个通过了TypeScript的检查。

function invertProperty<T, K extends keyof T> (o:T, propertyName:(T[K] extends boolean ? K : never)) {
  o[propertyName] = !o[propertyName]; // Type 'false' is not assignable to type 'T[T[K] extends boolean ? K : never]'. [2322]
}

const myObject:IObject = {
  a: 1,
  b: true,
  c: 'hi',
};

invertProperty(myObject, 'b'); // Argument of type '"b"' is not assignable to parameter of type 'never'. [2345]
invertProperty<IObject, 'b'>(myObject, 'b'); // Works, but requires me to type too much.
invertProperty(myObject, 'a'); // Argument of type '"a"' is not assignable to parameter of type 'never'. [2345]
invertProperty<IObject, 'a'>(myObject, 'a'); // Argument of type '"a"' is not assignable to parameter of type 'never'. [2345]

interface IObject {
  a:number,
  b:boolean,
  c:string,
}

我认为如果在K extends keyof T的类型约束中,我可以某种方式也说明and T[K] extends boolean它会做正确的事情。在我看来,这是一个问题,我试图在参数的类型中使用never,而不是能够约束类型参数。但我找不到任何表达方式。

关于如何通过全类型安全来实现这一目标的任何想法?

1 个答案:

答案 0 :(得分:3)

首先,您可以使用此构造提取布尔属性的所有键(它将非布尔值的键转换为never并使用所有键/永远的并集,使用T | never的事实T):

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

然后,为了让TypeScript乐于为属性赋值布尔值,你引入了声明只有布尔属性的中间类型(遗憾的是,TypeScript无法自己弄清楚这部分)

type OnlyBoolean<T> = { [k in BooleanKeys<T>]: boolean };

并声明invertProperty的泛型类型参数与OnlyBoolean兼容(它可能包含额外的非布尔属性,但它可以正常使用)

注意您可能需要不同版本的代码,具体取决于编译器的版本,此答案中的原始代码已停止使用TypeScript 3.2:

// for TypeScript 3.1 or earlier
function invertProperty<T extends OnlyBoolean<T>>(o: T, propertyName: BooleanKeys<T>) {
    o[propertyName] = !o[propertyName];
}

// for TypeScript 3.2 or later
function invertProperty<T>(o: OnlyBoolean<T>, propertyName: keyof OnlyBoolean<T>) {
    o[propertyName] = !o[propertyName];
}


interface IObject {
    a: number;
    b: boolean;
    c: string;
}
const myObject:IObject = {
  a: 1,
  b: true,
  c: 'hi',
};



invertProperty(myObject, 'b'); // ok
invertProperty(myObject, 'a'); // error