如何使用接口避免“后续属性声明必须具有相同的类型”

Ser*_*sky 2 overloading typescript

我有一些接口,它有一个枚举字段。还想为此接口进行重载,这将在某些特定枚举值的情况下再采用一个属性。

举个例子:

enum CellValueType {
    DATE,
    STRING,
    NUMBER
}


export interface TableCell {
    type: CellValueType;
    value: string; // ...
}

export interface TableCell {
    type: CellValueType.DATE;
    format: string; // Should be exist only on the DATE type
    value: string; // ...
}
Run Code Online (Sandbox Code Playgroud)

但是,问题是使用接口时出现错误:

后续的属性声明必须具有相同的类型。属性“type”必须是“CellValueType”类型,但此处的类型是“CellValueType.DATE”。

错误是可以理解的,没有疑问。

主要问题是如何通过合法途径避免这种情况的发生。

我知道,我可以使用type而不是interface得到这个结构:

export type TableCell = {
    type: CellValueType;
    value: string;
} | {
    type: CellValueType.DATE;
    format: string; 
    value: string;
}
Run Code Online (Sandbox Code Playgroud)

工作得很好,但问题是类无法实现联合类型。


未按预期工作,但DATE仍与第一个签名兼容。为了正确工作,需要将其type: CellValueType更改为type: Exclude<CellValueType, CellValueType.DATE>

另外,我只能为枚举类型创建一个子接口DATE,但也想避免为每种情况创建一个接口。

Ole*_*ter 5

当您以相同名称定义两个接口时,将执行声明合并。

export interface TableCell {
    type: CellValueType;
    value: string; // ...
}

export interface TableCell {
    type: CellValueType.DATE;
    format: string; // Should be exist only on the DATE type
    value: string; // ...
}
Run Code Online (Sandbox Code Playgroud)

CellValueType这是您在尝试与合并时遇到的设计行为CellValueType.DATE。引用手册

如果接口都声明同名但类型不同的非函数成员,编译器将发出错误

现在,当您尝试使用联合来创建所需的类型时,您遇到了另一种设计行为 - 由于 是 的CellValueType.DATE成员CellValueType,因此选择了更广泛的类型(想一想:A | ... | A | B与 相同A | B):

type isInType = CellValueType.DATE extends CellValueType ? true : false; //true;
type isNotInType = CellValueType extends CellValueType.DATE ? true : false; //false;
type cellType = TableCell["type"]; //CellValueType
Run Code Online (Sandbox Code Playgroud)

既然这样,为什么不想创建扩展基本类型的接口呢?这是一个完全有效的抽象策略。看一下下面的例子(注意断言as const,否则 的类型CellValueType.DATE将被扩展为CellValueType):

export enum CellValueType {
    DATE,
    STRING,
    NUMBER
}

interface TableCell {
    value: string;
}

export interface DateCell extends TableCell {
    type: CellValueType.DATE;
    format: string;
}

export interface NumberCell extends TableCell {
    //number-specific props here
}

export class DateCell implements DateCell {
    format = "yyyy-MM-dd";
    type = CellValueType.DATE as const;
    value = "2021-02-22";
}
Run Code Online (Sandbox Code Playgroud)

操场