我认为 TS 实际上是根据链接游乐场中的代码正确推断出您的类型。您的问题是您正在将 TypeScript 与这一行“混淆”(*混淆基于我对您的真实意图的理解):
type Widget = WidgetChart | WidgetBase;
具体而言,基于WidgetBase 的定义(在您的链接游乐场中),
enum WidgetType {
CHART = 'chart',
TABLE = 'table'
}
interface WidgetBase {
type: WidgetType
title: string
}
{ type: 'chart', title: 'some string' } 将是一个有效的 WidgetBase,因为Widget = WidgetChart | WidgetBase 这个对象也是一个有效的“Widget”!该对象匹配type === 'chart' 的switch case,并且不包含对chartType 的引用。所以 TypeScript 是“正确地”警告你 chartType 可能并不总是存在于具有“图表”类型的小部件上(因为你定义 Widget 的方式)。
因此,要更正它,您需要向 TS 提供实际的 WidgetTypes仅,即:
enum WidgetType {
CHART = 'chart',
TABLE = 'table'
}
enum ChartType {
LINE = 'line',
BAR = 'bar'
}
interface WidgetBase {
title: string
// ... other common, *generic* properties ...
}
interface WidgetTable extends WidgetBase {
type: WidgetType.TABLE
columnWidth: number
// ... other specific properties ...
}
interface WidgetChart extends WidgetBase {
type: WidgetType.CHART,
chartType: ChartType
// ... other specific properties ...
}
type Widget = WidgetChart | WidgetTable; // <-- There are only two choices now: either a valid WidgetChart or a valid WidgetTable, nothing else! You can add more WidgetTypes here in a similar manner of course if you want
function renderWidget(widget: Widget){
switch(widget.type) {
case WidgetType.CHART:
return console.log(widget.chartType); // this works now and doesn't complain :)
case WidgetType.TABLE:
return console.log(widget.columnWidth); // so does this! :)
// you don't _need_ a default here in this case, since you've covered all the WidgetTypes
}
}
编辑:另一种选择:
假设您有许多具有相同属性(类型除外)的不同小部件类型,并且只有少数需要附加属性,您可以采用稍微不同的方法。仅出于示例的目的,具有相同属性但类型不同的小部件将以不同的样式呈现,例如:type = 'card-big' 或 type = 'card-small' 等。
在这种情况下,您可能不希望使用上述方法必须指定 WidgetBase 的每个空扩展名。相反,您可以使用Exclude 和Omit TS Utility Types。工作代码将如下所示:
enum WidgetType {
CHART = 'chart',
TABLE = 'table',
CARD_BIG = 'card-big',
CARD_SMALL = 'card-small',
// ... etc ...
}
interface WidgetBase {
type: Exclude<WidgetType, WidgetType.CHART | WidgetType.TABLE>, // <-- this is the important bit - A generic widget is any widget *other than* chart or table
title: string
// ... other common, *generic* properties ...
}
interface WidgetTable extends Omit<WidgetBase, 'type'> { // <-- extend the base properties but omit the type which would clash otherwise
type: WidgetType.TABLE
columnWidth: number
// ... other specific properties ...
}
interface WidgetChart extends Omit<WidgetBase, 'type'> {
type: WidgetType.CHART,
chartType: ChartType
// ... other specific properties ...
}
type Widget = WidgetChart | WidgetTable | WidgetBase; // <-- specify the special cases + the generic case without confusing TS because WidgetBase no longer overlaps with WidgetChart etc.
function renderWidget(widget: Widget) {
switch(widget.type) {
case WidgetType.CHART:
return console.log(widget.title, widget.chartType); // your specific widgets have both generic and specific properties
case WidgetType.TABLE:
return console.log(widget.title, widget.columnWidth); // same as above
// ... handle other generic widgets if you want, or ...
default:
return console.log(widget.title) // all your other widgets would have only the generic properties
}
}