【问题标题】:Question concerning types, Maps and subclasses in typescript关于 typescript 中的类型、映射和子类的问题
【发布时间】:2021-12-12 17:05:28
【问题描述】:

我有一个Chart 类,它有几个扩展Chart 的子类(BarChartTimeseriesChart...)。 我使用一种称为buildChart 的方法来构建这些图表。它使用 Map 将枚举 ChartsEnum(例如 stackedTimeseriesbarChart)映射到正确的类:

export function buildChart(type: Charts, data: Array<TimeseriesData>) {
   
   var chartsMap = new Map<Charts, InstantiableAbstractClass<typeof Chart>([
        [Charts.stackedTimeseries, TimeseriesChart],
        [Charts.barChart, BarChart],
    ])
    const chart = chartsMap.get(type)
    return new chart(data).chart;

}

InstantiableAbstractClass 类型如下所示:

export declare type InstantiableClass<T> = (new ( ...args: any) => { [x: string]: any }) & T;

例如TimeseriesChart的类和构造函数如下所示:

export class TimeseriesChart extends Chart{
    constructor(data: Array<TimeseriesData>) {
        super(data);
    }
}

我现在想向chart 类添加第二个属性,称为options(在现有属性data 旁边)。 现在的问题是options 需要每个 ChartType(BarChartTimeseriesChart)不同的属性。 例如BarChart 需要这些属性:

{
    start: number;
    end?: number;
}

TimeseriesChart 需要这样的类型:

{
    description: string;
}

TimeseriesChart 的构造函数将如下所示:

export class TimeseriesChart extends Chart{
    constructor(data: Array<TimeseriesData>, options: TimeseriesChartOptions) {
        super(data, options);
    }
}

这意味着方法buildChart 需要一个新参数options,然后可以将其传递给特定的类(与参数data 一样)。

这样做的最佳方法是什么?我想过使用泛型,然后为 n 个子类定义 options 的 n 种类型,但我不知道如何为此正确更改类型 InstantiableAbstractClass。

您可以找到带有一些描述的完整示例here

非常感谢您的帮助!如果您需要任何进一步的信息,我很乐意提供。

谢谢你,一切顺利 卢卡斯

【问题讨论】:

  • 请提供minimal reproducible example,清楚地表明您面临的问题。理想情况下,有人可以将代码放入像The TypeScript Playground (link here!) 这样的独立IDE 中,然后立即着手解决问题,而无需首先重新创建它。所以应该没有错别字、不相关的错误或未声明/未导入的类型或值。
  • 嗨@jcalz,我编辑了代码,希望现在一切正常。你可以找到它here
  • 有空我会去看看,但你应该用你更新的代码和任何链接直接edit这个问题。
  • 在 cmets 中你说“我希望只有 options 只接受 TimeseriesChartOptions 类型的对象”但我不知道 TimeseriesChartOptions 是什么,你没有定义它.理想情况下,minimal reproducible example 应该有具体示例,其中某些内容与您想要的不同,其中不希望的结果以某种方式突出显示(例如,不应该出现错误,反之亦然)。
  • 嗨@jcalz。抱歉,感谢您的耐心等待:D Here 是更新后的游乐场。我希望它现在可以提供所有必需的信息。

标签: javascript typescript class generics types


【解决方案1】:

首先,您似乎希望您的Chart 类为abstract,因为您只会使用它的具体子类,并且您希望buildChart() 方法在没有被覆盖的情况下抛出。我还将使用parameter properties 作为声明字段和相同构造函数参数并将后者分配给前者的简写。我正在添加the any typeoptions 参数,因为我们真的不需要像这样在基类中进行任何类型检查。

abstract class Chart {
    constructor(protected data: Array<TimeseriesData>, protected options: any) { }
    abstract buildChart(): void;
}

无论如何,对于子类,我们将使用the declare property modifieroptions 属性缩小到适当的类型,并在构造函数参数中指定该类型:

interface BarChartOptions {
    start: number,
    end: number
}
class BarChart extends Chart {
    declare options: BarChartOptions;
    constructor(data: Array<TimeseriesData>, options: BarChartOptions) {
        super(data, options);
    }
    buildChart() { return {}; }
    protected getChartData() { }
}


interface TimeseriesChartOptions {
    description: string
}

class TimeseriesChart extends Chart {
    declare options: TimeseriesChartOptions;
    constructor(data: Array<TimeseriesData>, options: TimeseriesChartOptions) {
        super(data, options);
    }
    buildChart() { return { }; }
    protected getChartData() { }
}

现在我们需要表达您的Charts 枚举和不同子类之间的关系。让我们创建一个 chartConstructors 对象,将枚举作为键,将构造函数作为值:

const chartConstructors = {
    [Charts.stackedTimeseries]: TimeseriesChart,
    [Charts.barChart]: BarChart
}

您应该为您关心的Chart 的每个子类添加一个条目。


最后,我们将编写您的buildChart() 函数。让我们创建一些辅助类型:

type ChartConstructorParameters<C extends Charts> = 
  ConstructorParameters<typeof chartConstructors[C]>;
type ChartInstance<C extends Charts> = 
  InstanceType<typeof chartConstructors[C]>;
type ChartConstructor<C extends Charts> = 
  new (...args: ChartConstructorParameters<C>) => ChartInstance<C>;

ChartConstructorParameters&lt;C&gt; 类型将枚举成员作为C 并使用the ConstructorParameters&lt;T&gt; utility type 将其转换为相关类构造函数的tuple of arguments

ChartInstance&lt;C&gt; 类型对相关类构造函数的实例类型执行相同的操作,使用 the InstanceType&lt;T&gt; utility type

最后,ChartConstructor&lt;C&gt; 代表该类的相关 constructor signature

现在是buildChart()

function buildChart<C extends Charts>(
  type: C, ...ctorArgs: ChartConstructorParameters<C>
) {
    const chartConstructor = chartConstructors[type] as ChartConstructor<C>;
    return new chartConstructor(...ctorArgs);
}

这是一个generic function,它接受一个泛型类型Ctype参数,对应一个枚举成员。对于剩余的参数,它采用相关类的构造函数参数。这将是一对dataoptions 用于BarChartTimeseriesChart,但是如果您添加带有其他构造函数参数的子类,它将接受这些。

输出是类的一个实例,它通过从chartConstructors 获取相关构造函数来构造它(我们需要使用type assertion 来使编译器相信chartConstructors[type] 实际上是ChartConstructor&lt;C&gt; 类型;这是编译器无法看到的,因为它无法对未指定的泛型类型(如C)进行过多推理。


那么,它有效吗?

const barChart = buildChart(Charts.barChart, [], { start: 1, end: 2 });
// const barChart: BarChart

const timeSeriesChart = buildChart(Charts.stackedTimeseries, [], { description: "" });
// const timeSeriesChart: TimeseriesChart

是的,看起来不错!

Playground link to code

【讨论】:

    猜你喜欢
    • 2019-03-19
    • 2019-06-06
    • 2018-04-04
    • 1970-01-01
    • 2021-06-01
    • 1970-01-01
    • 2013-12-31
    • 2019-07-19
    • 1970-01-01
    相关资源
    最近更新 更多