TypeScript接口允许其他属性

142
总体来说,是否可能有一个声明一些基本属性但不限制附加属性的接口?这是我的当前情况:
我正在使用 Flux模式,它定义了一个通用的调度程序:
class Dispatcher<TPayload> {
    dispatch(arg:TPayload):void { }
}

我随后使用自己的有效负载类型创建一个调度程序,如下所示:
interface ActionPayload {
    actionType: string
}

const dispatcher = new Dispatcher<ActionPayload>();

现在我有一些动作代码,应该用一些附加数据分派有效载荷,但是 ActionPayload 接口只允许 actionType。换句话说,这段代码:
interface SomePayload extends ActionPayload {
    someOtherData: any
}

class SomeActions {
    doSomething():void {
        dispatcher.dispatch({
            actionType: "hello",
            someOtherData: {}
        })
    }
}

这会导致编译错误,因为someOtherDataActionPayload接口不匹配。问题在于许多不同的“action”类将重用相同的分发器,因此虽然这里是someOtherData,但可能是那边的anotherKindOfData等等。目前,我所能做的只是使用new Dispatcher<any>(),因为会分派不同的操作。不过所有的操作都共享一个基本的ActionPayload,所以我希望能够像new Dispatcher<extends ActionPayload>()这样约束类型。这种情况是否可能?

6个回答

263

1
谢谢,这是很好的信息。它恰好回答了我过于简化的标题...原来可以通过简单地转换对象文字来在我的问题细节中使用扩展<SomePayload>。对于这个笨拙的问题,我很抱歉。我应该尝试修复标题和摘要,例如“如何同时使用子接口和对象文字满足超级接口”,还是只接受您的答案? - Aaron Beall
对我来说,两者都可以。更改标题以反映问题可能更好。 - Sebastien
9
很遗憾,如果我们添加其他类型的字段,比如一个日期属性和动态字符串属性,这种方法就行不通了。 - K. D.
2
使用“unknown”类型比使用“any”类型更好。 - Burnee
@K.D. 只要索引器类型为任意或未知,它实际上是有效的。例如将其设置为 string 就会像你所说的那样。 - zr0gravity7

13
interface Options {
  darkMode?: boolean;
  [otherOptions: string]: unknown;
  }

4

如果使用TypeScript 3.9+并在tsconfig.json中将strict设置为true,则会出现已接受答案的错误。

以下是解决方案:


interface ActionPayload {
    actionType: string;

    // choose one or both depending on your use case
    // also, you can use `unknown` as property type, as TypeScript promoted type, 
    // but it will generate errors if you iterate over it
    [x: string]: any;
    [x: number]: any;
}

这可以避免出现以下错误:
  • 索引签名参数类型必须是“string”或“number”。
    • symbol不被允许
  • 索引签名参数类型不能是联合类型。考虑使用映射对象类型。
    • string | number[x: string | number]: unknown;中不被允许

3

我通过创建类似以下的内容解决了这个问题

type Make = "Volvo" | "Polestar" | "Saab";
interface BaseCar {
    make: Make;
    horsePower: number;
    allWheelDrive: boolean;
}
type Car = BaseCar & Record<string, string|number|boolean>;

2
这似乎无法工作:[https://www.typescriptlang.org/play?#code/C4TwDgpgBAsghga2gXigIgGoHsA2A3LNKAH3QAVcIBnYOAJyNLQGU44AjNAbgCgBLAHbAIdAGZwAxtABCcKhADC9KAG8eUDVAC2iCAC5Yu3pqgALLHXkUA7iIMCArlvYjjmuDhwB1UxAg4AETo+PH0odixKOAFeAF8eUEgoJTooVFl5FKgAMigAJQgJCwATAB4aYIEAcwAaKArBKoA+Xh4igRptEBSDLNQ1DR0kA0xcAjQa9TMLKyxbOgMAVgAGZcmND29ffyCQsOA6BwhJ2KA] - zr0gravity7

2
如果您的类型是从对象定义中推断出来的,您可以将类型断言为原始类型和{[key: string]: any]}的联合类型,具体请参见联合类型与交叉类型
const obj = { foo: 42 };
return obj as typeof obj & {[key: string]: any]};

这样,IntelliSense会建议您使用foo,但是当您尝试分配或检索另一个键时不会抱怨。

无论如何,如果额外的键很少并且它们是先验已知的,则可以在类型定义中将它们添加为可选项:

const obj: {foo: number; bar?: string} = {foo: 42}

2
这也可以写成 typeof obj & Record<string, any>。 - joeforker

0

我想我找到了我要找的东西。我可以将分派对象转换为SomePayload,并且TSC验证它与转换接口和分派器的TPayload兼容:

    dispatcher.dispatch(<SomePayload>{
        actionType: "hello",
        someOtherData: {}
    })

在线示例。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接