TypeScript细碎知识点:类型保护
类型保护是指缩小类型的范围,在一定的块级作用域内由编译器推导其类型,提示并规避不合法的操作,提高代码质量。
类型保护就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型。
我们可以通过typeof、instanceof、in、is和字面量类型将代码分割成范围更小的代码块,在这一块中,变量的类型是确定的。
typeof
⏰ typeof 操作符返回一个字符串,表示未经计算的操作数的类型。
TypeScript中的typeof主要用途是在类型上下文中获取变量或者属性的类型。 如:
-
获取变量类型
function fn(x: string | number) { if (typeof x === 'string') { x.toFixed(2); // Property 'toFixed' does not exist on type 'string'. return x.split(''); } // ... }
以上代码中,在if条件判断中使用
typeof判断 变量x的类型是否是string类型 -
获取对象的类型
interface Iperson { name: string, age: number; } let person: Iperson = { name: 'xman', age: 18 } type Person = typeof person; let p : Person = { name: 'ts', age: 20 }
以上代码中通过
对于嵌套对象也是一样:typeof获取到person对象的类型,之后我们就可以使用Person类型。
const userInfo = { name: 'xman', age: 18, address: { provice: '湖北', city: '武汉' } } type UserInfo = typeof userInfo;
此时UserInfo类型如下:
type UserInfo = { name: string; age: number; address: { provice: string; city: string; }; }
- 获取函数的类型
function add (x: number, y: number): number { return x + y; } type Add = typeof add;
此时Add类型为
type Add = (x: number, y: number) => number
instanceof
⏰ instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
TypeScript中 instanceof 与 typeof 类似,区别在于 typeof 判断基础类型,instanceof 判断是否为某个对象的实例。 其右侧要求是一个构造函数。
class Person { public name: string; public age: number; public constructor(theName: string, age: number) { this.name = theName; this.age = age; } } class Animal { public height: string; public weight: string; public constructor(height:string,weight: string) { this.height = height this.weight = weight } } function typeGuard (arg: Person | Animal) { if (arg instanceof Person) { arg.height = '60kg'; // Property 'height' does not exist on type 'Person'. } else if (arg instanceof Animal) { arg.name = '猴子'; // Property 'name' does not exist on type 'Animal'. } }
以上代码中, 通过 instanceof 判断变量arg是否为Person或者Animal对象的实例,从而进行不同的操作。
in
⏰ 如果指定的属性在指定的对象或其原型链中,则in 运算符返回true
🔔 注意:in右操作数必须是一个对象值。
TypeScript中 in 操作符用于确定属性是否存在于某个对象上, 这也是一种缩小范围的类型保护
class Person { public name: string; public age: number; public constructor(theName: string, age: number) { this.name = theName; this.age = age; } } class Animal { public height: string; public weight: string; public constructor(height: string, weight: string) { this.height = height; this.weight = weight; } } function typeGuard(arg: Person | Animal) { if ('name' in arg) { arg.name = 'xman'; } else if ('height' in Animal) { arg.height = '100kg'; } }
以上代码中, 通过 in 确定“属性”是否存在于Person或者Animal实例对象上,从而进行不同的操作。
类型谓词(is 关键字 )
类型谓词(type predicates): 为 parameterName is Type 这种形式。 parameterName必须来自于当前函数签名里的一个参数名。
⏰ is关键字一般用于函数返回值类型中,判断参数是否属于某一类型,并根据结果返回对应的布尔类型。
定义一个类型保护,只要简单地定义一个函数,其返回值是一个 类型谓词
class Fish { swim() { console.log('游泳~'); } eat() { console.log('进食!'); } } class Bird { fly() { console.log('飞翔~'); } eat() { console.log('进食!'); } } function getSmallPet(): Fish | Bird { return Math.random() > 0.5 ? new Fish() : new Bird() } let pet = getSmallPet(); pet.eat(); pet.swim(); // Property 'swim' does not exist on type 'Fish | Bird'. // Property 'swim' does not exist on type 'Bird'. pet.fly(); // Property 'fly' does not exist on type 'Fish | Bird'. // Property 'fly' does not exist on type 'Fish'.
以上代码中, getSmallPet函数中,即可以返回Fish类型对象,又可以返回Bird类型对象,由于返回对象类型不确定,所以使用联合类型对象共有的方法时,一切正常,但是使用联合类型对象各自独有的方法时,ts 会报错。 此时我们可以使用自定义类型保护来解决这个问题。
如下:
function isFish(pet: Fish | Bird): pet is Fish { return (pet as Fish).swim !== undefined; } if (isFish(pet)) { pet.swim(); } else { pet.fly(); }
常见的类型判断方法封装
-
hasOwn: 判断val是否含有属性key
const hasOwnProperty = Object.prototype.hasOwnProperty export const hasOwn = ( val: object, key: string | symbol ): key is keyof typeof val => hasOwnProperty.call(val, key)
-
isMap: 判断是否为Map
export const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === '[object Map]'
-
isSet: 判断是否为Set
export const isSet = (val: unknown): val is Set<any> => toTypeString(val) === '[object Set]'
-
isDate: 判断是否为日期
export const isDate = (val: unknown): val is Date => val instanceof Date
-
isFunction: 判断是否为function
export const isFunction = (val: unknown): val is Function => typeof val === 'function'
-
isString: 判断是否为string
export const isString = (val: unknown): val is string => typeof val === 'string'
-
isSymbol: 判断是否为symbol
export const isSymbol = (val: unknown): val is symbol => typeof val === 'symbol'
-
isObject: 判断是否为object且不是null
export const isObject = (val: unknown): val is Record<any, any> => val !== null && typeof val === 'object'
-
isPromise: 判断是否为promise
export const isPromise = <T = any>(val: unknown): val is Promise<T> => { return isObject(val) && isFunction(val.then) && isFunction(val.catch) }
-
isPlainObject: 判断是否为plainObject
export const objectToString = Object.prototype.toString export const toTypeString = (value: unknown): string => objectToString.call(value) export const isPlainObject = (val: unknown): val is object => toTypeString(val) === '[object Object]'
字面量类型保护
以下代码定义了两个接口和一个类别类型
interface Circle { kind: "circle"; // 字符串字面量类型 radius: number; } interface Square { kind: "square"; // 字符串字面量类型 sideLength: number; } type Shape = Circle | Square;
现在我们实现一个获取面积的方法:
function getArea(shape: Shape) { return Math.PI * shape.radius ** 2; // Property 'radius' does not exist on type 'Shape'. // Property 'radius' does not exist on type 'Square'. }
此时提示Square中不存在属性radius, 通过判断字面量类型来进行区分:
function getArea (shape: Shape) { switch (shape.kind) { case "circle": // Circle类型 return Math.PI * shape.radius ** 2; case "square": // Square类型 return shape.sideLength ** 2; } }
最后考虑default,可以利用never类型的特性实现全面性检查。
function getArea (shape: Shape) { switch (shape.kind) { case "circle": // Circle类型 return Math.PI * shape.radius ** 2; case "square": // Circle类型 return shape.sideLength ** 2; default: const _exhaustiveCheck: never = shape; return _exhaustiveCheck; } }
🔔 注意:never类型表示的是那些永不存在的值的类型。 但是如果又新增了联合类型, 但是忘记同时修改switch case分支控制流程, 最后shape就会被收窄为 Triangle 类型, 导致无法赋值给never类型,这时就会产生一个编译错误。
所以在使用 never类型时一定要避免出现新增了联合类型而没有对应的实现的情况。
interface Triangle { kind: "triangle"; sideLength: number; } type Shape = Circle | Square | Triangle; function getArea (shape: Shape) { switch (shape.kind) { case "circle": // Circle类型 return Math.PI * shape.radius ** 2; case "square": // Circle类型 return shape.sideLength ** 2; default: const _exhaustiveCheck: never = shape; // Type 'Triangle' is not assignable to type 'never'. return _exhaustiveCheck; } }
浙公网安备 33010602011771号