如何使用类型强制执行特定的参数对?

时间:2018-10-12 16:36:45

标签: typescript types

我有一个负责生成不同类型图表的服务。

可生成的图表空间具有两个维度chartTypedataType,它们都是有限的值集,如下所示:

enum ChartType {
  ChartTypeA,
  ChartTypeB,
  ChartTypeC
}

enum DataType {
  DataTypeA,
  DataTypeB,
  DataTypeC
}

该服务公开了一个公共方法generateChart(chartType: ChartType , dataType: DataType),然后根据传入的chartType调用相关的私有方法。

private方法的确切实现取决于另一个参数dataType

到目前为止很好。

以下是我的问题,(ChartType, DataType)的某些组合是不可能的(即我无法使用ChartTypeADataTypeC生成图表),这使我对当前的实现方式产生了疑问

有什么更好的方法来组织我的数据,以便编译器可以强制仅将可能的参数对传递给函数?

1 个答案:

答案 0 :(得分:1)

您可以对每个可能的有效组合使用重载:

enum ChartType {
    ChartTypeA,
    ChartTypeB,
    ChartTypeC
}

enum DataType {
    DataTypeA,
    DataTypeB,
    DataTypeC
}

function generateChart(chartType: ChartType.ChartTypeA, dataType: DataType.DataTypeA)
function generateChart(chartType: ChartType.ChartTypeC, dataType: DataType.DataTypeC)
function generateChart(chartType: ChartType.ChartTypeB, dataType: DataType.DataTypeB)
function generateChart(chartType: ChartType, dataType: DataType) { // Implementation signature

}

generateChart(ChartType.ChartTypeA, DataType.DataTypeA)
generateChart(ChartType.ChartTypeA, DataType.DataTypeC) // Error

或者我们可以使用映射类型来减少仪式:

interface EnuMap  {
    [ChartType.ChartTypeA]: DataType.DataTypeA,
    [ChartType.ChartTypeB]: DataType.DataTypeB,
    [ChartType.ChartTypeC]: DataType.DataTypeC,
}

function generateChart<T extends ChartType>(chartType: T, dataType: EnuMap[T])
function generateChart(chartType: ChartType, dataType: DataType) { // Implementation signature

}

generateChart(ChartType.ChartTypeA, DataType.DataTypeA)
generateChart(ChartType.ChartTypeA, DataType.DataTypeC) // Error

注意:如果我们将接口用于映射类型,则插件可以根据需要扩展该接口,例如,如果该插件广告支持新的类型组合。

修改

如果大多数组合都是可行的,并且应该排除少数组合,我们可以使用其他方法。首先创建一个包含所有可能的参数组合的类型,并使用Exclude取出不可能的组合:

function generateChart<T extends Excluded>(...a: T)
function generateChart(chartType: ChartType, dataType: DataType) { // Implementation signature

}
type AllCombinations = {
    [C in ChartType]: {
        [D in DataType]: [C, D]
    }
}[ChartType][DataType]
// Exclude unwanted combinations
type Excluded = Exclude<AllCombinations, [ChartType.ChartTypeA, DataType.DataTypeC]>; 

generateChart(ChartType.ChartTypeA, DataType.DataTypeA)
generateChart(ChartType.ChartTypeB, DataType.DataTypeA)
generateChart(ChartType.ChartTypeA, DataType.DataTypeC) // Error

通过这种方法,我们在参数名称中会失去一些提示性,并且编译器会建议重载(只是代码完成,它可以按预期工作)

可以使用here中的UnionToIntersection来构建一种可以更好地使用智能感知并保留参数名称的解决方案。我们首先创建所有可能签名的并集,然后使用UnionToIntersection创建具有所有重载的函数。

type AllCombinations = {
    [C in ChartType]: {
        [D in DataType]: [C, D]
    }
}[ChartType][DataType]
type Excluded = Exclude<AllCombinations, [ChartType.ChartTypeA, DataType.DataTypeC]>;
type UnionToIntersection<U> = 
    (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

type SignatureHelper<T> = T extends [infer C, infer D] ? (chartType: C, dataType: D) => void : never;
type GenerateChartType = UnionToIntersection<SignatureHelper<Excluded>>
const generateChart:GenerateChartType  = (chartType: ChartType, dataType: DataType) => { // Implementation signature

}
generateChart(ChartType.ChartTypeA, DataType.DataTypeA)
generateChart(ChartType.ChartTypeB, DataType.DataTypeA)
generateChart(ChartType.ChartTypeA, DataType.DataTypeC) // Error