在TypeScript中是否可以将字符串索引器限制为同级属性的名称和类型?

时间:2019-05-20 21:46:11

标签: typescript

上下文

在TypeScript中,可以这样声明字符串索引器:

class MyClass {
   prop1: CustomType1;
   prop2: CustomType2;
   [key: string]: any // <--- indexer
}

问题

是否可以声明具有以下特征的索引器而无需明确在签名上声明每个名称和类型?

  • 该键仅限于属性名称(例如:'prop1'|'prop2')
  • 类型受限于属性类型(例如:CustomType1 | CustomType2)。

例如,能否实现以下伪代码?

class MyClass {
   prop1: CustomType1;
   prop2: CustomType2;
   [key: MYCLASS PROP NAMES]: MYCLASS PROP TYPES // <--- pseudo-code 
}

感谢您的任何输入

1 个答案:

答案 0 :(得分:1)

我们添加一些类型定义,以便此代码进行编译:

interface CustomType1 { ct1: string };
interface CustomType2 { ct2: string };

正如我在评论中提到的那样,不需要为您的类型添加索引器,因为它已经具有您想要的行为。所以类型

type MyType = { 
   prop1: CustomType1,
   prop2: CustomType2
}

已经具有密钥"prop1" | "prop2"

type KeysOfMyType = keyof MyType; // "prop1" | "prop2"

并在CustomType1 | CustomType2的那些键处具有值:

type ValuesOfMyType = MyType[KeysOfMyType]; // CustomType1 | CustomType2

与索引签名最接近的是mapped type之类的Record<K, V>

type BlurredMyType = Record<KeysOfMyType, ValuesOfMyType>;
// type BlurredMyType = { 
//   prop1: CustomType1 | CustomType2; 
//   prop2: CustomType1 | CustomType2;
// }

但是,从某种意义上说,这样的类型是“模糊的”,即您无法再确定哪种属性类型与哪个键一起使用。尽管如此,MyType类型的任何值都应可分配给BlurredMyType类型的变量,因为“模糊”会产生超类型:

declare const myType: MyType;
const blurred: BlurredMyType = myType; // okay, no error

因此,如果您绝对需要这样的内容,可以将Blur<T>定义为

type Blur<T> = Record<keyof T, T[keyof T]>;

但是将T用作Blur<T>会失去属性读取的某些特性,并会失去属性写入的类型安全性:

declare const customType2: CustomType2;
blurred.prop1 = customType2; // no error, but now myType.prop1 is wrong

与其尝试扩大或更改类以包括索引签名,不如使用索引访问类型来适当地约束可能的键/值类型,这是更合理的。也就是说,使用K extends keyof MyClassMyClass[K]之类的类型来确保编译器正确访问了MyClass。这是一个使用此方法的示例,以及使用模糊类型的不安全方法:

class MyClass {
  prop1: CustomType1;
  prop2: CustomType2;
  constructor(prop1: CustomType1, prop2: CustomType2) {
    this.prop1 = prop1;
    this.prop2 = prop2;
  }
}

class AnotherClass {
  myClassInstance: MyClass;
  constructor(myClassInstance: MyClass) {
    this.myClassInstance = myClassInstance;
  }
  // generic method, fieldName and fieldValue are constrained
  method<K extends keyof MyClass>(fieldName: K, fieldValue: MyClass[K]) {
    this.myClassInstance[fieldName] = fieldValue;
  }

  // this works too but is not safe
  unsafeMethod(fieldName: keyof MyClass, fieldValue: MyClass[keyof MyClass]) {
      const myBlurredClassInstance: Blur<MyClass> = this.myClassInstance;
      myBlurredClassInstance[fieldName] = fieldValue;
  }
}


const ac = new AnotherClass(new MyClass({ct1: "a"}, {ct2: "b"}));
ac.method("prop1", {ct1: "okay"}); // okay
ac.method("prop2", {ct1: "whoops"}); // error
ac.unsafeMethod("prop1", {ct1: "okay"}); // okay
ac.unsafeMethod("prop2", {ct1: "whoops"}); // no error!

如您所见,method()调用会抱怨您传递的值和键不匹配,而unsafeMethod()调用则不匹配。即使使用“安全”方法,您也需要特别小心:

ac.method(Math.random()<0.5 ? "prop1" : "prop2", {ct1: "whoops"}); // no error!

在这种情况下,推断K的类型为"prop1" | "prop2",这对于属性写入是不安全的。就是这样。


无论如何,您可能不需要索引签名之类的东西;您只需要对索引访问使用适当约束的类型。希望能有所帮助;祝你好运!

Link to all code in Playground