在 typeScript 中,一个变量不会被限制为单一类型,如果你希望一个变量的值可以有多种类型,那么就可以使用 typeScript 提供的联合类型。
let str: string | string[]
当我们使用联合类型时,必须把当前值具体到实际类型,进行对应的操作。所以类型缩小就是从宽类型转化为窄类型的过程,常常用于处理联合类型变量的常量。
什么是类型缩小呢?
function getData( id: string|number ){
if( typeof id === "string" ){
cosole.log(id.toUpperCase())
}else{
cosole.log(id)
}
}
类型缩小:Type Narrowing。在实际应用中,我们经常会看到 typeof padding === "string" 的判断语句,根据不同的数据类型进行对应的处理,我们将这些特殊的检查称为类型防护,常见的类型防护有:
typeof 、平等缩小、instanceof、in 等等。
将类型细化为比声明更具体的类型的过程,称之为类型缩小。类型缩小的方法有很多,具体的有如下几种。
1、typeof 类型保护
在 TypeScript 中,typeof 类型守卫返回的是一个指定类型的字符串。
typeof 返回的值有:
object、string、number、boolean、undefined、function、bigint、symbol
function printAll(str: string | string[]) {
if (typeof str === "object") {
for(let s of str){
console.log(s);
}
}else if(typeof str === "string"){
console.log(str);
}
}
printAll("")
printAll(['a','b'])
printAll("12345")
2、真值缩小
在 typeScript 中可以使用:条件、&&、||、if语句、布尔否定(!)等,来组成布尔表达式。
function getUsersOnlineMessage(num: number) {
if (num) {
return `现在共有 ${num} 人在线!`
}
return "现在没人在线!"
}
getUsersOnlineMessage(NaN)
getUsersOnlineMessage(0)
getUsersOnlineMessage(null)
getUsersOnlineMessage(undefined)
上述方法四次调用,返回的都是“现在没人在线!”
NaN、0 、null 、undefined 、'' 、0n 这些类型作为 number 数据类型时,返回的都是 0 。所以执行结果都一样。
3、等值缩小
在 typeScript 中,经常使用 ===、!== 、== 、!= 运算符来进行等值检查,实现类型的缩小。
function example(x: string | number, y: string | boolean) {
if (x === y) {
x.toLocaleLowerCase()
y.toLocaleUpperCase()
} else {
//其他类型
}
}
4、in 操作符缩小
javaScript 中的 in 操作符,用于确定对象是否具有某名称的属性,如果指定的属性在对象或其原型链中,则返回 true 。
在 typeScript 中 in 操作符使用类似,使用语法为:
'name' in X
返回 true,表示 X 具有可选或必需属性或方法 name 。
返回false,表示需要具有可选或缺失属性的类型的值。
type Fish = {
swim: () => void
}
type Bird = {
fly: () => void
}
type Human = {
swim?: () => void
fly?: () => void
}
type Animal = Fish | Bird
function move(animal:Animal|Human) {
if("swim" in animal){
return (animal as Fish).swim()
}
return (animal as Bird).fly()
}
5、instanceof
instanceof 操作符,用来检查一个值是否是另一个值得实例。使用语法:
x instanceof Foo
检查 x 的原型链是否含有 Foo.prototype。
function show(x: Date | string) {
if (x instanceof Date) {
console.log(x.toUTCString());
} else {
console.log(x.toUpperCase());
}
}
show("abc") //ABC
show(new Date()) // Wed, 02 Feb 2022 08:55:09 GMT
6、类型谓词
为了定义一个用户定义的类型保护,只需要定义一个函数,让它的返回值是一个类型谓词就好了!使用语法:
parameteName is Type //parameteName 必须是函数内的参数。
type Fish = {
swim: () => void
}
type Bird = {
fly: () => void
}
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined
}
上述实例中,pet is Fish 就是所谓的自定义类型谓词。函数内部返回 true 就表示 pet is Fish,反之 pet is Bird 。
7、受歧视的 union
interface Shape {
kind: 'circle' | 'square',
radius?: number,
sideLength?: number
}
function getArea(shape: Shape) {
return Math.PI * Math.pow(shape.radius, 2)
}
console.log(getArea({ kind: 'square', sideLength: 5 }));
console.log(getArea({ kind: 'circle', radius: 10 }));
上述实例,只能在 strict :false 模式下正常运行,运行结果分别为:NaN 和 314.1592653589793.。
在 strict:true 模式下运行,会报错。如图:
由于 radius 和 sideLength 是可选参数,不传 radius 时,默认的 radius 就是 undefined ,此时类型不一致就报错了,两种模式下编译结果不同,所以称联合类型受到歧视。
在严格模式下,可以添加断言 !,解决此问题,如:
return Math.PI * Math.pow( shape.radius!, 2 )
!表示假设 shape.radius 是一定存在的,此时在严格模式下也不会报错了,与非严格模式运行结果一致。
上述联合类型出现的问题,具体的解决办法有:
interface Circle {
kind: "circle",
radius: number
}
interface Square {
kind: "square",
sideLength: number
}
type Shape = Circle | Square
function getArea(shape: Shape) {
switch (shape.kind) {
case 'circle':
return Math.PI * Math.pow(shape.radius, 2)
break;
case 'square':
return Math.pow(shape.sideLength, 2)
break
}
}
console.log(getArea({kind:"circle",radius:10}));
console.log(getArea({kind:"square",sideLength:10}));
也可以将 switch 改写成 if语句。
if (shape.kind === "circle") {
return Math.PI * Math.pow(shape.radius, 2)
} else {
return Math.pow(shape.sideLength, 2)
}
8、never 类型与穷尽性检查
never 表示不应该存在的状态,可以分配给每个类型,但是没有任何类型可以分配给 never ,除了 never 之外。具体而言,never 是永不返回函数的返回类型,也是变量在类型保护中永不为 true 的类型。
经常在 switch 中使用 never 进行详尽检查。修改上述实例,代码如下:
interface Circle {
kind: "circle",
radius: number
}
interface Square {
kind: "square",
sideLength: number
}
type Shape = Circle | Square
function getArea(shape: Shape) {
switch (shape.kind) {
case 'circle':
return Math.PI * Math.pow(shape.radius, 2)
break;
case 'square':
return Math.pow(shape.sideLength, 2)
break
break:
const exhaustiveCheck: never = shape
return exhaustiveCheck
}
}
此时代码运行正常,如果给 shape 类型中添加一个形状,代码如下:
interface Triangle {
kind: "triangle",
sideLength: number
}
type Shape = Circle | Square | Triangle
此时代码就会报错,提示如图:
此时 never 就会帮助我们做穷尽性检查,检查出来没有添加 triangle 类型判断。