【问题标题】:How to create a generic TypeScript interface which matches any type/interface/object with restrictions on types for values inside it?如何创建一个通用的 TypeScript 接口,该接口匹配任何类型/接口/对象,但对其中的值的类型有限制?
【发布时间】:2021-04-26 03:45:50
【问题描述】:

我想创建一个通用 TypeScript 接口,它匹配任何类型或接口或对象,但其中的值的类型有限制。

这里是MyInterface,它具有fooIPropbarIProp 的属性,其中存储了一个示例字符串:

interface MyInterface {
  fooIProp: string;
  barIProp: string;
};

const testInterface: MyInterface = {
  fooIProp: "foo",
  barIProp:  "bar"
};

这里是MyType 类型别名,它具有属性fooTPropbarTProp,其中存储字符串的示例:

type MyType = {
  fooTProp: string;
  barTProp: string;
}

const testType: MyType = {
  fooTProp: "foo",
  barTProp: "bar"
}

这是一个具有属性fooObjectKeybarObjectKey 的对象,用于存储字符串:

const testObject = {
  fooObjectKey: "foo",
  barObjectKey: "bar"
}

我创建了MyGenericInterface,它接受带有字符串作为键和值的对象,如下所示:

interface MyGenericInterface { [key: string]: string }

然后我尝试将testInterface 分配给MyGenericInterface

const testFromInterface: MyGenericInterface = testInterface;
const testFromType: MyGenericInterface = testType;
const testFromObject: MyGenericInterface = testObject;

它会抛出 TS2322 错误:

Type 'MyInterface' is not assignable to type 'MyGenericInterface'.
  Index signature is missing in type 'MyInterface'.(2322)

这里是TypeScript Playground 供参考。

问题:如何创建一个通用的 TypeScript 接口,它可以匹配任何类型/接口/对象,但对其中的值类型有限制?

【问题讨论】:

    标签: javascript typescript object types interface


    【解决方案1】:

    implicit index signatures 不会添加到 interface 类型的值中,而它们是为匿名对象类型添加的,这是一个已知问题(并且目前正在按预期工作)。请参阅microsoft/TypeScript#15300 进行讨论。


    从技术上讲,以这种方式添加隐式索引签名是不安全的。 TypeScript 中的对象类型是开放的/可扩展的,而不是封闭的/精确的(有关精确类型的讨论/请求,请参阅 microsoft/TypeScript#12936),因此除了在界面:

    const someObject = {
      fooIProp: "foo",
      barIProp: "bar",
      baz: 12345
    };
    const unexpectedMyInterface: MyInterface = someObject; // no error
    

    (尽管如果您尝试直接在带注释的对象字面量中添加此类未知属性,您将收到 excess property 警告,这有时会使 TypeScript 中的对象类型看起来准确,但实际上并非如此)。

    unexpectedMyInterface 可分配给MyInterface 但不能分配给MyGenericInterface 的事实是禁止分配在技术上是正确的原因。当然你也可以用MyType做同样的练习:

    const someOtherObject = {
      fooTProp: "foo",
      barTProp: "bar",
      baz: 12345
    };
    const unexpectedMyType: MyType = someOtherObject;
    

    ...但是将unexpectedMyType 分配给MyGenericInterface 的错误分配是允许的

    const badMyGenericInterface: MyGenericInterface = unexpectedMyType;
    console.log(badMyGenericInterface.baz?.toUpperCase()); // no error in TS, but 
    // RUNTIME ? badMyGenericInterface.baz.toUpperCase is not a function
    

    那么,什么给了?这似乎是一个权衡。 considered "safer" 允许匿名对象类型而不是接口类型这样做,因为接口类型可以在以后扩充或合并,而匿名对象类型不能。

    就我个人而言,我不认为它比另一个更安全。如果您想查看隐式索引签名也适用于接口类型,那么您可能需要转到 microsoft/TypeScript#15300 并给它一个 ?。


    除非该功能得到实施,否则您所能拥有的只是变通办法。一种是使用mapped typeMyInterface 的值转换为等效的对象类型,然后再将其分配给MyGenericInterface

    type Id<T> = { [K in keyof T]: T[K] } // change interface to mapped type version
    type MyInterfaceAsType = Id<MyInterface>;
    const testFromInterface2: MyGenericInterface = testInterface as MyInterfaceAsType
    const testInterfaceAnonymousTypeVersion: MyInterfaceAsType = testInterface;
    const testFromInterface3: MyGenericInterface = testInterfaceAnonymousTypeVersion;
    

    可能还有其他解决方法,但我需要先了解更多用例,然后再花太多时间提出建议。您在哪里看到了您想修复的故障?


    Playground link to code

    【讨论】:

    猜你喜欢
    • 2021-07-31
    • 1970-01-01
    • 2017-11-26
    • 1970-01-01
    • 1970-01-01
    • 2020-05-22
    • 2021-12-08
    • 2018-07-23
    • 2023-03-11
    相关资源
    最近更新 更多