使通用类型 Array<keyof T> 需要 T 的所有键

Make a generic type Array<keyof T> require all keys of T

我想声明一个类型,要求给定类型 T 的所有键都包含在一个数组中,例如:

checkKeys<T>(arr: Array<keyof T>): void {
  // do something
}

interface MyType {
  id: string;
  value: number;
}

目前如果调用checkKeys<MyType>,TS将认为传递的值如果包含MyTypeid | value)的任何键是有效的:

checkKeys<MyType>(['id', 'value']); // valid

checkKeys<MyType>(['id']); // valid

checkKeys<MyType>(['id', 'values']); // invalid

是否可以要求数组中指定所有键?

你不能用数组类型来做到这一点(至少我不知道有一种方法可以将键的并集扩展到元组类型中,可能有一种我只是不知道)。另一种方法是使用对象文字来实现类似的效果。语法有点冗长,但编译器将验证是否只指定了正确的键。我们将使用 Record 映射类型,我们可以对值使用 0 文字类型,因为只有键很重要。

function checkKeys<T>(o: Record<keyof T, 0>): void {
     // do something
}

interface MyType {
    id: string;
    value: number;
}

checkKeys<MyType>({ id: 0, value: 0 }); // valid

checkKeys<MyType>({ id: 0 }); // invalid

checkKeys<MyType>({ id: 0, values: 0 }); // invalid

我找到了一个解决方法,但实际上这个解决方案并不完美:

interface MyType {
  id: string;
  value: number;
}
const myType: MyType = {
   id: '',
   value: 0
};
type ArrType<T> = Array<keyof T>;
function isMyTypeArr<T>(arg: any[]): arg is ArrType<T> {
  return arg.length === Object.keys(myType).length;
}

function checkKeys<T>(arr: ArrType<T>): void {
  if (isMyTypeArr(arr)) {
    console.log(arr.length);
    // some other stuff
  }
}
checkKeys<MyType>(['id', 'x']); // TS error
checkKeys<MyType>(['id']); // no console because of Type Guard
checkKeys<MyType>(['id', 'value']); // SUCCESS: console logs '2'

想法是创建一个实现初始接口的简单对象。我们需要这个对象来获取它的键长度,以便在 isMyTypeArr Type Guard 中进行比较。 Type Guard 只是比较数组的长度——如果它们具有相同的长度,则意味着你提供了所有属性。


编辑

添加了另一个类似(更通用)的解决方案 - 主要区别是:

  • 将class与实现初始接口的构造函数参数一起使用;
  • 这个class有length属性(因为基本上它是一个构造函数)我们可以在我们的类型保护中使用它;
  • 我们还必须将 class 名称作为第二个参数传递,以获取其构造函数参数长度。我们不能为此使用泛型类型T,因为编译后的JS已将所有类型信息都删除,我们不能使用Tcheck this post for more deta

所以这是最终的解决方案:

interface IMyType {
  id: string;
  value: number;
}
class MyType implements IMyType {
  constructor(public id: string = '', public value: number = 0) {}
}
type ArrType<T> = Array<keyof T>;
function isMyTypeArr<T>(arg: ArrType<T>, TClass: new () => T): arg is ArrType<T> {
  return arg.length === TClass.length;
}

function checkKeys<T>(arr: ArrType<T>, TClass: new () => T): void {
  if (isMyTypeArr<T>(arr, TClass)) {
    console.log(arr.length);
    // some other stuff
  }
}

checkKeys<MyType>(['id', 'x'], MyType); // TS error
checkKeys<MyType>(['id'], MyType); // no console because of Type Guard
checkKeys<MyType>(['id', 'value'], MyType); // SUCCESS: console logs '2'

Notice that these examples are based on TypeScript issue 13267

p.s。还创建了两个示例的 stackblitz demo