类型爱护
类型爱护是指放大类型的范畴,在肯定的块级作用域内由编译器推导其类型,提醒并躲避不非法的操作,进步代码品质。
类型爱护就是一些表达式,它们会在运行时查看以确保在某个作用域里的类型。
咱们能够通过typeof
、instanceof
、in
、is
和字面量类型
将代码宰割成范畴更小的代码块,在这一块中,变量的类型是确定的。
typeof
先来看看JavaScript中typeof的用法:
具体可参考 MDN typeof
typeof 操作符返回一个字符串,示意未经计算的操作数的类型。
类型 | 后果 |
---|---|
Undefined | "undefined" |
Null | "object" |
Boolean | "boolean" |
Number | "number" |
BigInt(ECMAScript 2020 新增) | "bigint" |
String | "string" |
Symbol (ECMAScript 2015 新增) | "symbol" |
宿主对象(由 JS 环境提供) | 取决于具体实现 |
Function 对象 | "function" |
其余任何对象 | "object" |
// Undefinedtypeof undefined === 'undefined';typeof declaredButUndefinedVariable === 'undefined';typeof undeclaredVariable === 'undefined';typeof null === 'object';typeof false === 'boolean';typeof Boolean(1) === 'boolean'; // Boolean() 会基于参数是真值还是虚值进行转换// Symbolstypeof Symbol() === 'symbol';typeof Symbol('foo') === 'symbol';typeof Symbol.iterator === 'symbol';typeof 18n === 'bigint';// 字符串typeof 'hello' === 'string';typeof String(1) === 'string';// 数值typeof 99 === 'number';typeof NaN === 'number';typeof Number(1) === 'number'; // Number 会尝试把参数解析成数值// 对象 typeof {a: 1} === 'object';// 应用 Array.isArray 或者 Object.prototype.toString.call// 辨别数组和一般对象typeof [1, 2, 4] === 'object';typeof new Date() === 'object';typeof /regex/ === 'object';// 函数typeof function() {} === 'function';typeof class C {} === 'function'typeof Math.sin === 'function';
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(''); } // ...}
获取对象的类型
interface IPerson { name: string; age: number; }let person: IPerson = { name: 'xman', age: 18 };type Person = typeof person;let p: Person = { name: 'zxx', 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
先来看看JavaScript中instanceof的用法:
具体可参考 MDN instanceof
instanceof 运算符用于检测构造函数的 prototype 属性是否呈现在某个实例对象的原型链上。
简略例子如下:
// 定义构造函数function C(){}function D(){}var o = new C();o instanceof C; // true,因为 Object.getPrototypeOf(o) === C.prototypeo instanceof D; // false,因为 D.prototype 不在 o 的原型链上o instanceof Object; // true,因为 Object.prototype.isPrototypeOf(o) 返回 trueC.prototype instanceof Object // true,同上C.prototype = {};var o2 = new C();o2 instanceof C; // trueo instanceof C; // false,C.prototype 指向了一个空对象,这个空对象不在 o 的原型链上。D.prototype = new C(); // 继承var o3 = new D();o3 instanceof D; // trueo3 instanceof C; // true 因为 C.prototype 当初在 o3 的原型链上
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'. }}
in
先来看看JavaScript中in的用法:
具体可参考 MDN in
如果指定的属性在指定的对象或其原型链中,则in 运算符返回true
留神:
in右操作数必须是一个对象值。
简略例子如下:
const car = { make: 'Honda', model: 'Accord', year: 1998 };console.log('make' in car); // true;delete car.make;if ('make' in car === false) { car.make = 'Suzuki';}console.log(car.make); // Suzuki// 数组var trees = new Array("redwood", "bay", "cedar", "oak", "maple");0 in trees; // 返回 true3 in trees; // 返回 true6 in trees; // 返回 false"bay" in trees; // 返回 false (必须应用索引号,而不是数组元素的值)// 只是将一个属性的值赋值为undefined,而没有删除它,则 in 运算依然会返回truetrees[3] = undefined;3 in trees; // 返回 true"length" in trees; // 返回 true (length 是一个数组属性)Symbol.iterator in trees; // 返回 true (数组可迭代,只在 ES2015+ 上无效)// 内置对象"PI" in Math; // 返回 truevar color1 = new String("green");"length" in color1; // 返回 truevar color2 = "coral";"length" in color2; // 报错 (color2 不是对象)// 如果一个属性是从原型链上继承来的,in 运算符也会返回 true"toString" in {}; // 返回 true
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'; }}
类型谓词(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();}
拓展函数
const isNumber = (val: unknown): val is number => typeof val === 'number';const isString = (val: unknown): val is string => typeof val === 'string';const isSymbol = (val: unknown): val is symbol => typeof val === 'symbol';const isFunction = (val: unknown): val is Function => typeof val === 'function';const isObject = (val: unknown): val is Record<any, any> => val !== null && typeof val === 'object';function isPromise<T = any>(val: unknown): val is Promise<T> { return isObject(val) && isFunction(val.then) && isFunction(val.catch);}const objectToString = Object.prototype.toString;const toTypeString = (value: unknown): string => objectToString.call(value);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; }}
以上ts代码均在 https://www.typescriptlang.or... 上运行过,版本为4.7.2。
最初, 如有谬误,欢送各位大佬指导!感激!
参考资料
https://rangle.io/blog/how-to-use-typescript-type-guards/
https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates
https://juejin.cn/post/6974746024464089124#heading-4