类型缩小
什么是类型缩小呢?
-
类型缩小的英文是 Type Narrowing;
-
我们可以通过类似于 typeof padding === "number" 的判断语句,来改变TypeScript的执行路径;
-
在给定的执行路径中,我们可以缩小比声明时更小的类型,这个过程称之为 缩小;
-
而我们编写的 typeof padding === "number 可以称之为 类型保护(type guards);
常见的类型保护有如下几种:
typeof
平等缩小(比如===、!==)
instanceof
in,等等
typeof
在 TypeScript 中,检查返回的值typeof是一种类型保护:因为 TypeScript 对如何typeof操作不同的值进行编码。
type IDType = number | string
function printID(id: IDType) {
if (typeof id === 'string') {
console.log(id.toUpperCase())
} else {
console.log(id)
}
}
平等缩小
平等缩小是指使用 ===、==、!==、!=、switch 等运算符进行类型缩小。在给定的代码中,我们使用了 Direction 类型的值进行了平等缩小。具体来说,我们使用了 switch 运算符进行了类型缩小。
type Direction = "left" | "right" | "top" | "bottom"
function printDirection(direction: Direction) {
switch (direction) {
case 'left':
console.log(direction)
break;
case 'right':
console.log(direction)
break;
case 'top':
console.log(direction)
break;
case 'bottom':
console.log(direction)
break;
}
}
instanceof
在这个例子中,我们使用了 instanceof 运算符对 Student 和 Teacher 类型进行了缩小。
class Student {
studying() {}
}
class Teacher {
teaching() {}
}
function work(p: Student | Teacher) {
if (p instanceof Student) {
p.studying()
} else {
p.teaching()
}
}
const stu = new Student()
work(stu)
in
在这个例子中,我们使用了 in 运算符对 Fish 和 Dog 类型进行了缩小。
type Fish = {
swimming: () => void
}
type Dog = {
running: () => void
}
function walk(animal: Fish | Dog) {
if ('swimming' in animal) {
animal.swimming()
} else {
animal.running()
}
}
const fish: Fish = {
swimming() {
console.log("swimming")
}
}
walk(fish)
函数类型
在 TypeScript 中, 函数也是一种类型, 可以像其它类型一样被使用.
函数类型的定义
函数类型的定义包括两部分: 参数类型和返回值类型.
例如, 下面是一个函数类型的定义:
type AddFnType = (num1: number, num2: number) => number
函数类型的应用
在 TypeScript 中, 可以使用 type 关键字定义一个函数类型, 然后使用这个类型定义常量.例如, 在以下代码中, AddFnType 是一个函数类型, add 是一个常量, 它的类型是 AddFnType
type AddFnType = (num1: number, num2: number) => number
const add: AddFnType = (a1: number, a2: number) => {
return a1 + a2
}
参数的默认值
从ES6开始,JavaScript是支持默认参数的,TypeScript也是支持默认参数的
在 TypeScript 中, 函数的参数可以设置默认值.
例如, 下面的代码中, name参数的默认值是 'world':
function sayHello(name = 'world') {
console.log(`Hello, ${name}!`)
}
sayHello() // 输出: Hello, world!
sayHello('TypeScript') // 输出: Hello, TypeScript!
如果调用 sayHello函数时不传入参数, 那么 name参数的值就是 'world'. 如果传入了参数, 那么 name参数的值就是传入的参数.
注意, 如果一个参数设置了默认值, 那么它就变成了可选参数.也就是说, 在调用函数时可以不传入这个参数.
例如, 上面的代码中, name参数设置了默认值, 所以在调用sayHello函数时可以不传入这个参数。
如果不想让一个参数成为可选参数, 可以将它的默认值设置为undefined.例如, 下面的代码中, name参数的默认值是 undefined, 所以在调用 sayHello函数时必须传入这个参数:
function sayHello(name: string = undefined) {
console.log(`Hello, ${name}!`)
}
sayHello('TypeScript') // 输出: Hello, TypeScript!
sayHello() // Error: Expected 1 arguments, but got 0.
剩余参数
在 TypeScript 中, 可以使用 rest 参数语法将这些参数整合成一个数组.console.log会把我们传入的参数放到一个数组里进行操作。
function log(message: string, ...optionalParams: any[]) {
console.log(message, ...optionalParams)
}
log('Hello, TypeScript!', 10, true, 'foo')
function sum(initalNum: number, ...nums: number[]) {
let total = initalNum
for (const num of nums) {
total += num
}
return total
}
console.log(sum(20, 30))
console.log(sum(20, 30, 40))
console.log(sum(20, 30, 40, 50))
函数重载
使用联合类型实现不同的函数:
function add(a1: number | string, a2: number | string) {
if (typeof a1 === "number" && typeof a2 === "number") {
return a1 + a2
} else if (typeof a1 === "string" && typeof a2 === "string") {
return a1 + a2
}
// return a1 + a2;
}
add(10, 20)
使用联合类型进行逻辑判断的缺点有以下两个:
1.代码可读性差
使用联合类型进行逻辑判断时,需要进行很多的类型缩小操作,这样会导致代码可读性变差,增加代码的维护成本。
2.返回值类型不确定
使用联合类型进行逻辑判断时,函数的返回值类型依然是不能确定的,这样会导致代码的可靠性变差,增加代码的调试成本。
为了解决这两个问题,我们可以使用函数重载来实现函数的多态性,这样既可以提高代码的可读性,又可以确定函数的返回值类型。
function add(a1: number, a2: number): number;
function add(a1: string, a2: string): string;
function add(a1: any, a2: any): any {
if (typeof a1 === "number" && typeof a2 === "number") {
return a1 + a2
} else if (typeof a1 === "string" && typeof a2 === "string") {
return a1 + a2
}
}
add(10, 20) // 返回30
add("hello", "world") // 返回"helloworld"
function add(a1: any, a2: any): any这个函数是不能单独调用的比如add(0,"1"),因为唯一调用的两种方法都声明了function add(a1: number, a2: number): number;function add(a1: string, a2: string): string;只能同时传入两个number或者两个string.