TypeScript中的原子类型判别(标称原子类型)

时间:2019-05-18 13:40:10

标签: typescript

我只是好奇者,有没有一种方法可以区分原子类型以提高TypeScript的类型安全性?

换句话说,下面是否可以复制行为:

export type Kilos<T> = T & { discriminator: Kilos<T> };   // or something else  
export type Pounds<T> = T & { discriminator: Pounds<T> }; // or something else

export interface MetricWeight {
    value: Kilos<number>
}

export interface ImperialWeight {
    value: Pounds<number>
}

const wm: MetricWeight = { value: 0 as Kilos<number> }
const wi: ImperialWeight = { value: 0 as Pounds<number> }

wm.value = wi.value;                  // Should give compiler error
wi.value = wi.value * 2;              // Shouldn't error, but it's ok if it would, because it would require type casting which asks for additional attention
wm.value = wi.value * 2;              // Already errors
const we: MetricWeight = { value: 0 } // Already errors

或者可以将其放入一个容器的东西:

export type Discriminate<T> = ...

export type Kilos<T> = Discriminate<Kilos<T>>;
export type Pounds<T> = Discriminate<Pounds<T>>;

...

修改

好吧,事实证明,可以使用ZpdDG4gta在此处https://github.com/microsoft/TypeScript/issues/202发现的不可能的类型hack来构建这种类型。

但是当前语言版本有点混乱:

export type Kilos<T> = T & { discriminator: any extends infer O | any ? O : never };
export type Pounds<T> = T & { discriminator: any extends infer O | any ? O : never };

export interface MetricWeight {
    value: Kilos<number>
}

export interface ImperialWeight {
    value: Pounds<number>
}

const wm: MetricWeight = { value: 0 as Kilos<number> }
const wi: ImperialWeight = { value: 0 as Pounds<number> }

wm.value = wi.value;                       // Errors, good
wi.value = wi.value * 2;                   // Errors, but it's +/- ok
wi.value = wi.value * 2 as Pounds<number>; // Shouldn't error, good
wm.value = wi.value * 2;                   // Errors, good
const we: MetricWeight = { value: 0 }      // Errors, good

不幸的是,以下操作无效:

export type Discriminator<T> = T & { discriminator: any extends infer O | any ? O : never } 

export type Kilos<T> = Discriminator<T>;
export type Pounds<T> = Discriminator<T>;

export interface MetricWeight {
    value: Kilos<number>
}

export interface ImperialWeight {
    value: Pounds<number>
}

const wm: MetricWeight = { value: 0 as Kilos<number> }
const wi: ImperialWeight = { value: 0 as Pounds<number> }

wm.value = wi.value;                       // Doesn't error, this is bad
wi.value = wi.value * 2;                   // Errors, but it's +/- ok
wi.value = wi.value * 2 as Pounds<number>; // Shouldn't error, good
wm.value = wi.value * 2;                   // Errors, good
const we: MetricWeight = { value: 0 }      // Errors, good

修改

事实证明,按照@jcalz,还有另一种方法来引入不可能的类型:

export type Kilos<T> = T & { readonly discriminator: unique symbol };
export type Pounds<T> = T & { readonly discriminator: unique symbol };

...

但是仍然缺少

export type Discriminator<T> = ...

有什么想法使其变得更清洁?由于类型别名使两个类型引用都坚持使用Discriminator ...

1 个答案:

答案 0 :(得分:0)

只需将其定义为:

Save

然后,磅和公斤会自动投射到数字和数字上,而不是彼此投射。

const marker = Symbol();

export type Kilos = number & { [marker]?: 'kilos' };
export const Kilos = (value = 0) => value as Kilos;

export type Pounds = number & { [marker]?: 'pounds' };
export const Pounds = (value = 0) => value as Pounds;

如果要防止自动将“增强”单位转换为“普通”数字,则只需从标记字段中删除可选内容即可:

let kilos = Kilos(0);
let pounds = Pounds(0);
let wrong: Pounds = Kilos(20); // Error: Type 'Kilos' is not assignable to type 'Pounds'.

kilos = 10; // OK
pounds = 20;  // OK

let kilos2 = 20 as Kilos; // OK
let kilos3: Kilos = 30; // OK

pounds = kilos;  // Error: Type 'Kilos' is not assignable to type 'Pounds'.
kilos = pounds; // Error: Type 'Pounds' is not assignable to type 'Kilos'.

kilos = Kilos(pounds / 2); // OK
pounds = Pounds(kilos * 2); // OK

kilos = Pounds(pounds / 2); // Error: Type 'Pounds' is not assignable to type 'Kilos'.

kilos = pounds / 2; // OK
pounds = kilos * 2; // OK