共计 7244 个字符,预计需要花费 19 分钟才能阅读完成。
类型爱护
类型爱护是指放大类型的范畴,在肯定的块级作用域内由编译器推导其类型,提醒并躲避不非法的操作,进步代码品质。
类型爱护就是一些表达式,它们会在运行时查看以确保在某个作用域里的类型。
咱们能够通过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” |
// Undefined
typeof undefined === 'undefined';
typeof declaredButUndefinedVariable === 'undefined';
typeof undeclaredVariable === 'undefined';
typeof null === 'object';
typeof false === 'boolean';
typeof Boolean(1) === 'boolean'; // Boolean() 会基于参数是真值还是虚值进行转换
// Symbols
typeof 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.prototype
o instanceof D; // false,因为 D.prototype 不在 o 的原型链上
o instanceof Object; // true,因为 Object.prototype.isPrototypeOf(o) 返回 true
C.prototype instanceof Object // true,同上
C.prototype = {};
var o2 = new C();
o2 instanceof C; // true
o instanceof C; // false,C.prototype 指向了一个空对象,这个空对象不在 o 的原型链上。D.prototype = new C(); // 继承
var o3 = new D();
o3 instanceof D; // true
o3 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; // 返回 true
3 in trees; // 返回 true
6 in trees; // 返回 false
"bay" in trees; // 返回 false (必须应用索引号,而不是数组元素的值)
// 只是将一个属性的值赋值为 undefined,而没有删除它,则 in 运算依然会返回 true
trees[3] = undefined;
3 in trees; // 返回 true
"length" in trees; // 返回 true (length 是一个数组属性)
Symbol.iterator in trees; // 返回 true (数组可迭代,只在 ES2015+ 上无效)
// 内置对象
"PI" in Math; // 返回 true
var color1 = new String("green");
"length" in color1; // 返回 true
var 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