class 中 keyof 的模板文字

Template literal from keyof in class

我正在尝试同时组合一堆 TypeScript 功能。

我创建了一个 class,它是用一个对象初始化的,并且有一堆函数,这些函数应该只能将那个对象的键作为参数。到目前为止,一切都很好。 ,我的基本设置如下所示:

class MyClass<Type> {
  properties: Partial<Type> = {};

  constructor(properties: Type) {
    this.properties = properties;
  }
  
  pick(...propNames: Array<keyof Type>) {
    return propNames.reduce((obj, name) => ({ 
      ...obj, 
      [name]: this.properties[name]
    }), {} as Partial<Type>);
  }
}

但我实际上想要 密钥本身,以及这些密钥的略微修改版本被接受——具体来说,"key""key!"。在我的例子中,如果您向密钥附加感叹号,则该(可接受的)输入用于指示需要该密钥。

我在手册中找到template literals,发现我可以这样做:

type Name = 'John';
type BangName = `${Name}!`;

完美!但是,我不知道如何将它集成到我的用例中,其中第一种类型是从 keyof 生成的,并且这种集成发生在 JS class.

我看到了 ,接近了。它表明我可以做类似下面的事情,它生成一个组合接口,我可以使用 keyof:

从中导出可接受的密钥
type BangType = {
  [Key in keyof Type as `${Key}!`]: Type[Key];
}

type CombinedType = Type & BangType;

然后:

pick(...propNames: Array<keyof CombinedType>) {

但我不知道在哪里我可以生成这个CombinedType,因为通用Type在 class 签名中定义。这可能吗?

以下似乎不起作用:

pick(...propNames: Array<keyof PropType> | Array<Key in keyof PropType as `${Key}!`>)

你确实可以在这里使用template literal types。如果您希望 propNameskeyof T 或“如果将 "!" 附加到 keyof T 中字符串的末尾所获得的字符串”的数组,则类型你要找的是

Array<keyof T | `${Extract<keyof T, string>}!`>

请注意,由于 T 是一些 generic type you don't control, it's possible for it to have some symbol-valued keys. And since keyof T may have symbols in it,如果您尝试直接编写模板文字类型 `${keyof T}!`,编译器将不高兴,因为模板文字类型只能序列化strings 和 numbers(呃,bigints,booleans,nullundefined。仅此而已):

oops(...propNames: Array<keyof T | `${keyof T}!`>) { } // error!
// ---------------------------------> ~~~~~~~
// 'symbol' is not assignable to 'string | number | bigint | boolean | null | undefined'.

因为我们只对 Tstring 值键感兴趣,所以我们使用 the Extract<T, U> utility type 来获取这些键。然后编译器不会抱怨:

pick(...propNames: Array<keyof T | `${Extract<keyof T, string>}!`>) {
  return propNames.map(k =>
    typeof k === "string" ? k.replace(/!$/, "") as keyof T : k
  ).reduce((obj, name) => ({
    ...obj,
    [name]: this.properties[name]
  }), {} as Partial<T>);
}

让我们确保它有效:

const n = new MyClass({ a: 1, b: "two", c: true });    
const x = n.pick("b!");
console.log(x) // {b: "two"}

看起来不错!

Playground link to code