索引类型映射可以扩展为区分联合吗?

时间:2017-04-04 03:00:13

标签: typescript

我们说我有以下类型地图:

type MyTypes = {
  'float': number;
  'text': string;
  'bool': boolean;
  'price': number;
  'date': Date;
};

我想为此自动生成一个有区别的联合类型。相当于:

type Datum<K, V> = { type: K, value: V };

type MagicUnionMaker<TypeMap> = (
  // pretend the below is auto-generated from TypeMap
  Datum<'float', number> |
  Datum<'text', string> |
  Datum<'bool', boolean> |
  Datum<'price', number> |
  Datum<'date', Date>
);

可以这样使用:

interface DataModel<TypeMap> {
  data(row: number, col: number): MagicUnionMaker<TypeMap>;
}

let model: DataModel<MyTypes>;
let datum = model.data(0, 0);
switch (datum.type) {
  case 'float':
    // datum.value is `number`
    break;
  case 'text':
    // datum.value is `string`
    break;
  case 'bool':
    // datum.value is `boolean`
    break;
  case 'price':
    // datum.value is `price`
    break;
  case 'date':
    // datum.value is `Date`
    break;
  case 'thing': // error
    break; 
}

目前这样的事情可能吗?

2 个答案:

答案 0 :(得分:3)

不完全是,您似乎无法拥有通用的UnionMaker,并且需要在每次需要时就地构建联合类型:

type MyTypes = {
  'float': number;
  'text': string;
  'bool': boolean;
  'price': number;
  'date': Date;
};

type TypeDataMap<T> = {[K in keyof T]: {type: K, value: T[K]}};

interface MyDataModel {
  data(row: number, col: number): TypeDataMap<MyTypes>[keyof MyTypes];
}

let model: MyDataModel;
let datum = model.data(0, 0);
// type inference works as expected 
switch (datum.type) {
  case 'float':
     let n: number = datum.value;
    break;
  case 'text':
     let s: string = datum.value;
    break;
  case 'bool':
    let b: boolean = datum.value;
    break;
  case 'price':
    let p: number = datum.value;
    break;
  case 'date':
     let d: Date = datum.value;
    break;
  case 'thing': // error: Type '"thing"' is not comparable to type 
                // '"float" | "text" | "bool" | "price" | "date"'.
    break; 
}

尝试构造等效的泛型类型失败:

// does not work
type UnionMaker<T> = TypeDataMap<T>[keyof T]; 

type MyTypeMap = UnionMaker<MyTypes>;
// because MyTypeMap gets inferred for some reason as 
// type MyTypeMap = { 
//     type: "float" | "text" | "bool" | "price" | "date"; 
//     value: string | number | boolean | Date; 
//  }
// which is NOT what you want

答案 1 :(得分:0)

似乎这可能在Typescript 2.8中有条件类型,参见https://github.com/Microsoft/TypeScript/pull/21316

npm install typescript@next之后,你可以这样做:

 type MyTypes = {
    'float': number;
    'text': string;
    'bool': boolean;
    'price': number;
    'date': Date;
 };

 type Datum<K, V> = { t: K, v: V };
 type FromKeys<T, U> = T extends keyof U ? Datum<T, U[T]> : never;
 type UnionMaker2<T, U> = FromKeys<keyof T, U>;
 type UnionMaker<T> = UnionMaker2<T, T>;

 const x1: UnionMaker<MyTypes> = {t: 'text', v: 'string'};
 const x2: UnionMaker<MyTypes> = {t: 'bool', v: true};
 // fails, as expected:
 // const x3: UnionMaker<MyTypes> = {t: 'text', v: true};