在编程中,我们经常会遇到一些变量或属性只能取有限的几个值的情况。例如,一个页面的方向只能是“横向”或“纵向”。那么,如何在TypeScript中限制变量只能取这些有限的值呢?这就需要用到单例类型和区分联合的概念。
单例类型与区分联合
在TypeScript中,单例类型是指那些只包含一个值的类型。例如,'landscape'
和'portrait'
都是单例类型。当我们把多个单例类型组合在一起时,就形成了区分联合。区分联合是一种类型模式,它通过一个共同的属性(称为区分符)来区分不同的类型。
示例1:打印机页面方向
我们先来看一个简单的例子,假设我们有两个打印机类,一个用于打印横向页面,另一个用于打印纵向页面:
class PrinterA {
pageOrientation: 'landscape';
printLandscape(): void {
console.log("printing in landscape");
}
}
class PrinterB {
pageOrientation: 'portrait';
printPortrait(): void {
console.log("printing in portrait");
}
}
在上面的例子中,pageOrientation
就是单例类型。我们可以通过一个函数来根据页面方向调用不同的打印方法:
function print(pt: PrinterA | PrinterB): void {
if(pt.pageOrientation === 'landscape'){
pt.printLandscape();
}else if(pt.pageOrientation === 'portrait'){
pt.printPortrait();
}else{
let unknownPrinter: never = pt;
}
}
如果我们在函数中遗漏了某个页面方向的处理,TypeScript编译器会报错。例如,如果我们删除了处理'portrait'
的else if
分支,编译器会报错:
build/discriminated-unions2.ts(24,13): error TS2322: Type 'PrinterB' is not assignable to type 'never'.
如果调用了错误的方法,编译器也会报错:
function doPrint(pt: PrinterA | PrinterB): void {
if (pt.pageOrientation === 'landscape') {
pt.printLandscape();
} else if (pt.pageOrientation === 'portrait') {
pt.printLandscape(); // 错误的方法
} else {
let unknownPrinter: never = pt;
}
}
编译器会报错:
build/discriminated-unions3.ts(21,12): error TS2339: Property 'printLandscape' does not exist on type 'PrinterB'.
由于TypeScript在编译时会对单例值进行检查,我们的IDE也能够正确地提供代码自动补全,并且在出现错误时给出提示。
使用类型别名
除了直接使用联合类型,我们还可以定义类型别名以便复用。例如,我们定义了三种员工类型:
interface FullTimeEmployee {
empType: "FullType";
name: string;
annualSalary: number;
}
interface PartTimeEmployee {
empType: "PartTime";
name: string;
monthlySalary: number;
}
interface ContractEmployee {
empType: "Contractor";
name: string;
hourlySalary: number;
}
// 使用类型别名
type Employee = FullTimeEmployee | PartTimeEmployee | ContractEmployee;
function getEmployeeSalary(emp: Employee): number {
switch (emp.empType) {
case "FullType":
return emp.annualSalary;
case "PartTime":
return emp.monthlySalary;
case "Contractor":
return emp.hourlySalary;
default:
let temp: never = emp;
return temp;
}
}
let contractor: ContractEmployee = {empType: "Contractor", name: "Tina", hourlySalary: 34};
let sal = getEmployeeSalary(contractor);
console.log(sal); // 输出:34
在这个例子中,我们使用了switch
语句来代替if/else
语句,效果是一样的。
使用枚举
枚举常量也可以作为区分符。例如,我们定义了两种形状类型:
enum ShapeType {TRIANGLE, RECTANGLE}
interface RightAngledTriangle {
shapeType: ShapeType.TRIANGLE;
base: number;
height: number;
hypotenuse: number;
}
interface Square {
shapeType: ShapeType.RECTANGLE;
length: number;
width: number;
}
type Shape = RightAngledTriangle | Square;
function getArea(shape: Shape): number {
switch (shape.shapeType) {
case ShapeType.TRIANGLE:
return (shape.base * shape.height) / 2;
case ShapeType.RECTANGLE:
return shape.length * shape.width;
default:
let temp: never = shape;
return temp;
}
}
let shape: Square = {shapeType: ShapeType.RECTANGLE, length: 5, width: 5};
let area = getArea(shape);
console.log(area); // 输出:25
在这个例子中,shapeType
就是区分符,我们通过它来区分不同的形状类型,并计算相应的面积。
总结
通过单例类型和区分联合,我们可以方便地限制变量只能取有限的值,并且能够在编译时检查代码的正确性。这不仅提高了代码的可维护性,还减少了运行时错误的可能性。