【问题标题】:TypeScript not inferring correct interface based on propertyTypeScript 没有根据属性推断正确的接口
【发布时间】:2021-12-07 21:02:11
【问题描述】:

我一直遇到与 TS 相同的“问题”,其中我有对象列表,每个对象都有基于其类型的不同属性。例如:

const widgets = [
  {type: 'chart', chartType: 'line'},
  {type: 'table', columnWidth: 100}
];

我可以为上面创建接口和类型并且效果很好,但最后我总是遇到 TS 不够“智能”以识别我正在处理的对象类型的问题。见this playground example

所以最后我总是要做类似的事情

(widget as WidgetChart).chartType

查看一些 SO 问题,我发现了 @matthew-mullin 的 very similar in this answer 哪个说

现在您可以使用 Sublist 类型,它会根据您提供的字段值正确推断它是 SublistPartners 还是 SublistItem 类型。

但显然我的情况并没有发生这种情况。

是我做错了什么还是我对 TS 期望过高?

【问题讨论】:

    标签: typescript


    【解决方案1】:

    我认为 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 的每个空扩展名。相反,您可以使用ExcludeOmit 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
        }
    }
    

    【讨论】:

    • 任何有兴趣的人,这里是solution
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-06-28
    • 1970-01-01
    • 2019-10-24
    • 1970-01-01
    • 2021-11-03
    • 2019-04-03
    • 2014-11-28
    相关资源
    最近更新 更多