内容简介:在 TypeScript 中,一个变量不会被限制为单一的类型。如果你希望一个变量的值,可以有多种类型,那么就可以使用 TypeScript 提供的联合类型。下面我们来举一个联合类型的例子:当我们使用联合类型时,我们必须尽量把当前值的类型收窄为当前值的实际类型,而类型保护就是实现类型收窄的一种手段。A type guard is some expression that performs a runtime check that guarantees the type in some scope. —— Ty
在 TypeScript 中,一个变量不会被限制为单一的类型。如果你希望一个变量的值,可以有多种类型,那么就可以使用 TypeScript 提供的联合类型。下面我们来举一个联合类型的例子:
let stringOrBoolean: string | boolean = "Semlinker"; interface Cat { numberOfLives: number; } interface Dog { isAGoodBoy: boolean; } let animal: Cat | Dog;
当我们使用联合类型时,我们必须尽量把当前值的类型收窄为当前值的实际类型,而类型保护就是实现类型收窄的一种手段。
二、类型保护
A type guard is some expression that performs a runtime check that guarantees the type in some scope. —— TypeScript 官方文档
类型保护是可执行运行时检查的一种表达式,用于确保该类型在一定的范围内。换句话说,类型保护可以保证一个字符串是一个字符串,尽管它的值也可以是一个数值。类型保护与特性检测并不是完全不同,其主要思想是尝试检测属性、方法或原型,以确定如何处理值。目前主要有四种的方式来实现类型保护:
1. in 关键字
interface Admin { name: string; privileges: string[]; } interface Employee { name: string; startDate: Date; } type UnknownEmployee = Employee | Admin; function printEmployeeInformation(emp: UnknownEmployee) { console.log("Name: " + emp.name); if ("privileges" in emp) { console.log("Privileges: " + emp.privileges); } if ("startDate" in emp) { console.log("Start Date: " + emp.startDate); } }
2. typeof 关键字
function padLeft(value: string, padding: string | number) { if (typeof padding === "number") { return Array(padding + 1).join(" ") + value; } if (typeof padding === "string") { return padding + value; } throw new Error(`Expected string or number, got '${padding}'.`); }
typeof
类型保护只支持两种形式: typeof v === "typename"
和 typeof v !== typename
, "typename"
必须是 "number"
, "string"
, "boolean"
或 "symbol"
。 但是 TypeScript 并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护。
3. instanceof 关键字
interface Padder { getPaddingString(): string; } class SpaceRepeatingPadder implements Padder { constructor(private numSpaces: number) {} getPaddingString() { return Array(this.numSpaces + 1).join(" "); } } class StringPadder implements Padder { constructor(private value: string) {} getPaddingString() { return this.value; } } let padder: Padder = new SpaceRepeatingPadder(6); if (padder instanceof SpaceRepeatingPadder) { // padder的类型收窄为 'SpaceRepeatingPadder' }
4. 自定义类型保护的类型谓词(type predicate)
function isNumber(x: any): x is number { return typeof x === "number"; } function isString(x: any): x is string { return typeof x === "string"; }
相信除了类型谓词外,大家对其它三种方式都很熟悉了,下面我们来着重介绍一下类型谓词。
三、类型谓词
在开始介绍类型谓词前,我们先来看一个示例:
interface Vehicle { move: (distance: number) => void; } class Car implements Vehicle { move = (distance: number) => { // Move car… }; turnSteeringWheel = (direction: string) => { // Turn wheel… }; } class VehicleController { vehicle: Vehicle; constructor(vehicle: Vehicle) { this.vehicle = vehicle; } } const car = new Car(); const vehicleController = new VehicleController(car); const { vehicle } = vehicleController; vehicle.turnSteeringWheel('left');
尽管你知道汽车是一辆车,但 VehicleController
已经把它简化为一辆基本的汽车。因为 Vehicle 并没有 turnSteeringWheel
属性,所以对于以上代码,TypeScript 编译器会提示以下错误信息:
Property 'turnSteeringWheel' does not exist on type 'Vehicle'.
对于这个问题,我们可以利用 instanceof
关键字来确保当前的对象是 Car
汽车类的实例:
if(vehicle instanceof Car) { vehicle.turnSteeringWheel('left'); }
但该方案有一定的限制,即它只对类有效。当判断的对象不是某个类的实例时就无效了,比如:
const anotherCar = { move: (distance: number) => null, turnSteeringWheel: (direction: string) => null }; const anotherVehicleController = new VehicleController(anotherCar); const { vehicle } = anotherVehicleController; if (vehicle instanceof Car) { vehicle.turnSteeringWheel('left'); console.log("这是一辆车"); } else { console.log("这不是一辆车"); }
尽管 anotherCar
跟前面已经定义的 car
拥有相同的形状,但它并不是 Car
汽车类的实例,因此在这种情况下, vehicle instanceof Car
表达式返回的结果为 false。所以以上代码的输出结果是:”这不是一辆车”。
尽管 typeof
和 instanceof
这两个关键字在很多情况下可以满足类型保护的需求,但在函数式编程的领域它们的功能就受限了。那么我们应该如何检查任何对象的类型的?幸运的是,你可以创建自定义类型保护。
3.1 自定义类型保护
下面我们继续以车辆和汽车的例子为例,来创建一个自定义类型保护函数 —— isCar
,它的具体实现如下:
function isCar(vehicle: any): vehicle is Car { return (vehicle as Car).turnSteeringWheel !== undefined; }
你可以传递任何值给 isCar 函数,用来判断它是不是一辆车。isCar 函数与普通函数的最大区别是,该函数的返回类型是 vehicle is Car
,这就是我们前面所说的 “类型谓词”。
在 isCar 函数的方法体中,我们不仅要检查 vehicle
变量是否含有 turnSteeringWheel
属性,而且还要告诉 TS 编译器,如果上述逻辑语句的返回结果是 true,那么当前判断的 vehicle
变量值的类型是 Car 类型。
现在让我们来重构一下前面的条件语句:
// vehicle instanceof Car -> isCar(anotherCar) if (isCar(anotherCar)) { anotherCar.turnSteeringWheel('left'); console.log("这是一辆车"); } else { console.log("这不是一辆车"); }
在重构完成后,我们再次运行代码,这时控制台会输出 “这是一辆车”。好了,现在问题已经解决了。接下来让我们来总结一下自定义类型保护有什么用?
3.2 自定义类型保护有什么用
自定义类型保护的主要特点是:
vehicle is Car (vehicle as Car).turnSteeringWheel !== undefined
对于基本数据类型来说,我们也可以自定义类型保护来保证类型安全,比如:
const isNumber = (variableToCheck: any): variableToCheck is number => (variableToCheck as number).toExponential !== undefined; const isString = (variableToCheck: any): variableToCheck is string => (variableToCheck as string).toLowerCase !== undefined;
如果你要检查的类型很多,那么为每种类型创建和维护唯一的类型保护可能会变得很繁琐。针对这个问题,我们可以利用 TypeScript 的另一个特性 —— 泛型,来解决复用问题:
function isOfType<T>( varToBeChecked: any, propertyToCheckFor: keyof T ): varToBeChecked is T { return (varToBeChecked as T)[propertyToCheckFor] !== undefined; }
在以上代码中,我们定义了一个通用的类型保护函数,你可以在需要的时候使用它来缩窄类型。以前面自定义类型保护的例子来说,我们就可以按照以下方式来使用 isOfType
通用的类型保护函数:
// isCar(anotherCar) -> isOfType<Car>(vehicle, 'turnSteeringWheel') if (isOfType<Car>(vehicle, 'turnSteeringWheel')) { anotherCar.turnSteeringWheel('left'); console.log("这是一辆车"); } else { console.log("这不是一辆车"); }
有了 isOfType
通用的类型保护函数之后,你不必再为每个要检查的类型编写唯一的类型保护函数。而且在实际的开发过程中,只要我们合理的使用类型保护函数,就可以让我们的代码在运行时能够保证类型安全。
四、参考资源
以上所述就是小编给大家介绍的《在 TS 中如何实现类型保护?类型谓词了解一下》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 在 TS 中如何实现类型保护?类型谓词了解一下
- 论文浅尝 | 基于未知谓词与实体类型知识图谱的 Zero-Shot 问题生成
- Java 8谓词链
- java8-谓词(predicate)
- 【大数据】SparkSql连接查询中的谓词下推处理(一) 原 荐
- 【大数据 】SparkSQL连接查询中的谓词下推处理(二) 原 荐
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。