基础概念

JavaScript 的缺陷

任何新技术的出现都是为了解决原有技术的某个痛点。
ES6、7、8等的推出,每次都会让 JavaScript 这门语言更加现代、更加安全、更加方便。但是直到今天,JavaScript 在类型检测上依然是毫无进展。
image.png

认识 TypeScript

image.png

TypeScript 的特点

image.png

大前端的发展趋势

image.png

TypeScript 的编译运行环境

编译环境

我们需要在电脑上安装TypeScript,这样就可以通过TypeScript的Compiler将其编译成JavaScript;
image.png
所以,我们需要先可以先进行全局的安装:

  1. # 安装命令
  2. npm install typescript -g
  3. # 查看版本
  4. tsc --version

运行环境

如果我们每次为了查看TypeScript代码的运行效果,都通过经过两个步骤的话就太繁琐了:

  • 第一步:通过tsc编译TypeScript到JavaScript代码;
  • 第二步:在浏览器或者Node环境下运行JavaScript代码;

是否可以简化这样的步骤呢?

  • 比如编写了TypeScript之后可以直接运行在浏览器上?
  • 比如编写了TypeScript之后,直接通过node的命令来执行?

上面我提到的两种方式,可以通过两个解决方案来完成:

  • 方式一:通过webpack,配置本地的TypeScript编译环境和开启一个本地服务,可以直接运行在浏览器上;
  • 方式二:通过ts-node库,为TypeScript的运行提供执行环境;

webpack配置

方式一在之前的TypeScript文章中我已经有写过,如果需要可以自行查看对应的文章;
https://mp.weixin.qq.com/s/wnL1l-ERjTDykWM76l4Ajw

使用 ts-node

  1. # 安装ts-node
  2. npm install ts-node -g
  3. # 另外ts-node需要依赖 tslib 和 @types/node 两个包:
  4. npm install tslib @types/node -g
  5. # 现在,我们可以直接通过 ts-node 来运行TypeScript的代码:使用ts-node
  6. ts-node math.ts

变量的声明

声明了类型后 TypeScript 就会进行类型检测,声明的类型可以称之为类型注解

  • var/let/const 标识符: 数据类型 = 赋值;

注意数据类型的大小写,小写就是 ts 定义的数据类型,大写指的是 ECMA 定义的包装类。

  1. let msg1: string = "hhh" // string是TypeScript中定义的字符串类型
  2. let msg2: String = "hhh" // String是ECMAScript中定义的一个类

在 tslint 中并不推荐使用var来声明变量。

变量的类型推导(推断)

定义变量的时候,我们会初始化变量。给变量初始化时的数据是什么数据类型,则 ts 就会推断这个变量的类型就是初始化时的类型。这样就可以方便不需要将类型写出来。

  1. let msg = 123 // 初始化是数字,则推断为 number 类型
  2. msg = "hhh" // 报错

数据类型

JavaScript和TypeScript的数据类型

TypeScript是JavaScript的一个超集,数据类型自然也是一样。
image.png

JavaScript 中的类型

number 类型

image.png

boolean 类型

image.png

string 类型

image.png

Array 类型

数组类型除了定义自身是个数组类型外,还需要定义数组内容是什么类型。另外在 js 中尽管没有类型概念,但是最好存放在数组中的类型是同一类型。

定义数据类型有两种方式:

  1. const names1: Array<string> = [] // 不推荐(react jsx中是有冲突 <div></div>)
  2. const names2: string[] = [] // 推荐
  3. names.push("abc")
  4. names.push(123) // 报错

Object 类型

对象类型没有意义,因为对象本身就是作为一个复杂数据类型出现,所以对象中可以存任意类型值。
具体到 ts 中,对象中的键值对会自动推断类型。

  1. const obj = {
  2. name: 'zs',
  3. age: 30
  4. }
  5. //类型
  6. const obj = {
  7. name: string;
  8. age: number;
  9. }

Symbol 类型

image.png

null 和 undefined 类型

js 中的 6 个假值:false、null、0、""、undefined、NaN。空数组[]和空对象{}都为真值 true。
js 中有 null 和 undefined 两个类型,ts 也有,并且这两个类型的值也只有 null 和 undefined。

注意:null 的类型推断,因为我们可能用 null 初始化变量,但是实际想要保存的是其他类型的值。所以 ts 就把 null 值的类型推断为 any。

  1. let msg = null
  2. msg = 123 // 成功
  3. // 显示确定类型
  4. let hhh: null = null
  5. hhh = 123 // 报错

如果是其他类型,也是可以用 null 进行赋值的,但是开启 ts 的严格模式后编译会报错。严格模式默认开启,也可以在 tsconfig.json 中配置关闭。

TypeScript 中额外的类型

any 类型

因为类型推断的存在,其实 ts 中的变量已经不能随意接受其他值了。如果我们想要让变量不受类型限制,就可以设置类型为 any,回到最初的模样。

  1. // 在不想给某些JavaScript添加具体的数据类型时(和原生的JavaScript代码是一样)
  2. let message: any = "Hello World"
  3. message = 123
  4. message = true
  5. message = { }

unknown 类型

unknown是TypeScript中比较特殊的一种类型,它用于描述类型不确定的变量。
any 也是描述类型不确定的变量,那这和 unknown 有啥区别?
any 和最初的 js 一样,没有任何限制。想干嘛干嘛。但是 unknown 它限制了 unknown 类型的变量只能赋值给 unknown 类型或者 any 类型。这就防止了它到处乱用。

  1. function foo() {
  2. return "abc"
  3. }
  4. function bar() {
  5. return 123
  6. }
  7. // unknown类型只能赋值给any和unknown类型
  8. // any类型可以赋值给任意类型
  9. let flag = true
  10. let result: unknown // 最好不要使用any
  11. if (flag) {
  12. result = foo()
  13. } else {
  14. result = bar()
  15. }
  16. let message: string = result // 报错

void 类型

void通常用来指定一个函数是没有返回值的,那么它的返回值就是 void 类型。
函数我们没有写任何返回类型,那么它默认返回值的类型就是 void ,我们也可以显示的来指定返回值是 void:

  • js 没写返回值,默认返回 undefined ```javascript // 显示指出返回类型为 void function foo(num: number): void { console.log(num) }

// 默认返回 void function fn(num: number) { console.log(num) }

  1. <a name="d0zjv"></a>
  2. ### never 类型
  3. 表示永远不会发生,当一些错误可能逃逸的时候,我们就能在错误逃逸的执行路径上增加一个违反 never 语句。当错误逃逸,按这条路径执行到 never 的语句,就会因为 never 报错,而终止执行。<br />相当于把 never 当一个报警器了。
  4. ```javascript
  5. // 假如封装了一个核心函数
  6. // 参数类型只支持 string 和 number
  7. function handleMessage(message: string | number) {
  8. switch (typeof message) {
  9. case 'string':
  10. console.log("string处理方式处理message")
  11. break
  12. case 'number':
  13. console.log("number处理方式处理message")
  14. break
  15. default:
  16. }
  17. }
  18. // 张三找到了源码,并且给函数的参数类型添加一个可选的 boolean,然后传入布尔值。
  19. function handleMessage(message: string | number | boolean)
  20. handleMessage(true)
  21. // 这个时候,函数可以接收 bool 值,但其实函数内部并不会对 bool 值有任何处理,而且函数还不报错
  22. // 为了规范使用者使用我们的库函数,可以增加容错措施
  23. function handleMessage(message: string | number | boolean) {
  24. switch (typeof message) {
  25. case 'string':
  26. console.log("string处理方式处理message")
  27. break
  28. case 'number':
  29. console.log("number处理方式处理message")
  30. break
  31. default:
  32. // 增添容错检测,正常传入 string 和 number 不会执行到这
  33. // 一旦传入其他类型,就会执行到这,但是赋值操作是 never永不发生,所以会报错
  34. const check: never = message
  35. }
  36. }

tuple 类型

元组类型允许表示一个已知元素数量和类型的数组,很多语言中也有这种数据类型,比如Python、Swift等。
之前定义数组类型:const x: string[];表示数组中元素类型必须都是 string。而 tuple 类型可以让我们定制元素类型。
根据索引值获取到的元素可以得到对应的类型;
元组类型的数组定义时是有确切长度的,如果越界添加元素,则新元素类型必须是前面元素类型中的一种。

  1. // Declare a tuple type
  2. let x: [string, number];
  3. // Initialize it
  4. x = ['hello', 10]; // OK
  5. // Initialize it incorrectly
  6. x = [10, 'hello']; // Error
  7. // 越界添加元素
  8. x[3] = 'hhh' // ok,因为 x[3] 能接收的类型为 string|number
  9. x[4] = false // error

那么tuple在什么地方使用的是最多的呢?

  • tuple通常用来定义返回值的类型,在使用的时候会非常的方便;

比如一个函数它返回一个数组,数组里面有一个 any 类型的元素和一个函数。现在要 ts 要返回这个数组,那怎么定义这个数组呢?

  1. 定义成 any 类型的数组,可以解决问题,但是失去类型控制的意义了,回到解放前的 js
  2. 不返回数组了,返回一个对象,值和函数作为对象的属性。Java 就是这么干的,但是不够灵活,因为用的时候必须使用对象中定义好的 key,要不然访问不到对象中的属性值。
  3. 返回 tuple 类型的数组,只规定了类型,用户拿到了可以随便取名字用。 ```javascript // 定义一个函数,接收一个值,返回一个数组,数组中第一个元素为原来的值,第二个元素为设置新值的函数 function useState(state: any) { let currentState = state const changeState = (newState: any) => { currentState = newState }

    // 定义数组 tuple,第一个元素为任意值,第二个为函数:该函数接收一个任意值参数,并且无返回值 const tuple: [any, (newState: any) => void] = [currentState, changeState] return tuple }

// 解构数组获取里面的元素 const [counter, setCounter] = useState(10); setCounter(1000)

// 使用时,可以随意取名接收元素,比对象的方式灵活简洁 const [title, setTitle] = useState(“abc”)

  1. <a name="A4Gt6"></a>
  2. # 函数中的类型
  3. <a name="AUreC"></a>
  4. ## 参数类型
  5. 声明函数时,可以在每个参数后添加类型注解,以声明函数接受的参数类型:
  6. ```javascript
  7. function sum(num1: number, num2: number) {
  8. return num1 + num2
  9. }
  10. // 参数类型和个数必须正确
  11. sum(1, 2)

函数的返回值类型

我们也可以添加返回值的类型注解,这个注解出现在函数列表的后面:

  1. function sum(num1: number, num2: number): number {
  2. return num1 + num2
  3. }

和变量的类型注解一样,我们通常情况下不需要返回类型注解,因为 TypeScript 会根据 return 返回值推断函数的
返回类型。某些第三方库处于方便理解,会明确指定返回类型,但是这个看个人喜好。

匿名函数的参数

匿名函数的参数类型,因为执行环境可能不一样,所以我们不仅可以给它明确指定,还可以让它自己根据上下文推导参数类型。我们也可以称这种类型为上下文类型。

  1. const names: string[] = ["abc", "cba", "nba"]
  2. // item根据上下文的环境推导出来的, 这个时候可以不添加的类型注解
  3. names.forEach(function(item) { // 很明显 names 中都是 string,所以 item 不用手动指定类型
  4. console.log(item.split(""))
  5. })

对象类型

如果我们希望限定一个函数接受的参数是一个对象,这个时候要如何限定呢?
我们可以使用对象类型;

  1. // Point: x/y -> 对象类型
  2. // {x: number, y: number}
  3. function printPoint(point: {x: number, y: number}) {
  4. console.log(point.x);
  5. console.log(point.y)
  6. }
  7. printPoint({x: 123, y: 321})

在这里我们使用了一个对象来作为类型:

  • 在对象我们可以添加属性,并且告知TypeScript该属性需要是什么类型;
  • 属性之间可以使用 , 或者 ; 来分割,最后一个分隔符是可选的;
  • 每个属性的类型部分也是可选的,如果不指定,那么就是any类型;

    可选类型

    对象类型也可以指定哪些属性是可选的,可以在属性的后面添加一个?: ```javascript function printPoint(point: {x: number, y: number, z?: number}) { // z 可选 console.log(point.x) console.log(point.y) console.log(point.z) }

printPoint({x: 123, y: 321}) printPoint({x: 123, y: 321, z: 111})

function foo(msg?: string) { // msg 可选 console.log(msg) } foo() foo(‘hhh’)

  1. <a name="vGxQn"></a>
  2. ## 联合类型
  3. 联合类型(Union Type)是由两个或者多个其他类型组成的类型;表示变量可以是这些类型中的任何一个类型;联合类型中的每一个类型被称之为联合成员(union's members );
  4. 联合类型虽然不像 any 一样没有限制,但类型依然具有不确定性,还是可能带来问题。所以代码逻辑中依然需要判断实参具体是联合类型中的哪一种,这种代码结构我们称为`narrow 缩小`,因为它缩小了类型范围。
  5. ```javascript
  6. // number|string|boolean 联合类型
  7. function printID(id: number|string|boolean) {
  8. // 使用联合类型的值时, 需要特别的小心
  9. // narrow: 缩小
  10. if (typeof id === 'string') {
  11. // 确定了 id 一定是string类型
  12. console.log(id.toUpperCase())
  13. } else {
  14. console.log(id)
  15. }
  16. }
  17. printID(123)
  18. printID("abc")
  19. printID(true)

其实可选类型本质就是联合类型联合了 undefined,联合类型需要显示传入 undefined,不能不传入任何参数,而可选类型可以什么都不传入,其实默认传入了 undefined。

  1. function foo(msg?: string)
  2. function foo(msg: string|undefined)

类型别名

联合类型或者对象类型,写起来太长了,不直观。所以可以给类型起个别名,类似 C语言中的 #define。
关键字为type

  1. // type用于定义类型别名(type alias)
  2. type IDType = string | number | boolean // 可选类型别名
  3. type PointType = { // 对象类型别名
  4. x: number
  5. y: number
  6. z?: number
  7. }
  8. function printId(id: IDType) {
  9. }
  10. function printPoint(point: PointType) {
  11. }

类型断言 as

有时候TypeScript无法获取具体的类型信息,这个我们需要使用类型断言(Type Assertions)。
TypeScript只允许类型断言转换为 更具体 或者 不太具体 的类型

  • 语法:as 类型

比如我们通过 document.getElementById,TypeScript只知道该函数会返回 HTMLElement ,但并不知道它
具体的类型:

  1. // 假设 why 所在的元素为 img,但是 ts 不知道
  2. const el = document.getElementById("why") as HTMLImageElement // 断言明确元素类型为 img
  3. // 一般的元素没有 src 属性,所以 ts 会报错
  4. el.src = "url地址"

断言也会多应用于多态。

  1. class Person { }
  2. // Student 继承 Person
  3. class Student extends Person {
  4. studying() {
  5. console.log(123);
  6. }
  7. }
  8. function sayHello(p: Person) {
  9. (p as Student).studying() // 接收父类,然后断言向下转型具体的子类才能调用子类的方法
  10. }
  11. sayHello(new Student()) // 123
  1. // 3.了解: as any/unknown
  2. const message = "Hello World"
  3. const num: number = (message as unknown) as number

非空类型断言 !

我们确定传入的参数是有值的,这个时候我们可以使用非空类型断言:
非空断言使用的是! ,表示可以确定某个标识符是有值的,跳过ts在编译阶段对它的检测

  1. function printMessageLength(message?: string) {
  2. // message 是可选的,如果没传入,则是 undefined.length 报错,所以 ts 编译不通过
  3. // console.log(message.length)
  4. // if (message) { // 可以 if 判断增添容错
  5. // console.log(message.length)
  6. // }
  7. console.log(message!.length) // 非空断言,断定 message 为非空,ts 校验通过
  8. }

我对 ts 编译器断言,我断定这个形参有值,你只管按我的决定去办。
但是我可能断言错了,逃过了编译,实际执行时还是报错,所以严谨的写法是使用可选链。

可选链的使用

可选链并不是 TypeScript 独有的特性,它是ES11(ES2020)中增加的特性:
可选链使用可选链操作符 ?.
它的作用是当对象的属性不存在时,会短路,表达式直接返回undefined,如果存在,那么才会继续执行;

  1. // info 是一个隐藏的对象
  2. const info = {
  3. name: "why",
  4. // friend: {
  5. // girlFriend: {
  6. // name: "hmm"
  7. // }
  8. // }
  9. }
  10. // 我们预期 info 对象有 friend 属性,但是某种原因没了
  11. // 那就会立即因 undefined.girlFriend 导致报错,终止后面代码的执行
  12. console.log(info.friend.girlFriend.name)
  13. // 之前会用 &&与运算 和 if 先判断属性是否存在,但是这样太烦了
  14. if (info && info.friend && info.friend.girlFriend) {
  15. console.log(info.friend.girlFriend.name)
  16. }
  17. // ES11 提供了可选链(Optional Chainling)
  18. // 以 ?. 的方式调用,若中间某个属性不存在,则会立即停止调用,并返回 undefined,不影响后续代码
  19. console.log(info.friend?.girlFriend?.name) // undefined
  20. console.log('其他的代码逻辑')

字面量类型

字面量类型就是相当于自己定义了一个类型,且属于这个类型的值只有定义类型时的值。
let msg: hhh = 'hhh'hhh 就是字面量类型,并且属于 hhh 类型的值只有 hhh。
其实 const 定义的变量,ts 类型推导后的都是字面量类型:
const msg = 'hhh'msg 的类型推导出来的不是 string,而是字面量类型 hhh。

这种字面量类型就像 const 一样定义不可修改的变量,有啥意义呢?
它的意义就是和联合类型一起达到类似枚举的效果。

  1. type method = 'GET'|'POST'
  2. function request(requestMethod: method) // requestMethod 的取值就只能是 get 或者 post 了

字面量推理

来看下面的代码:

  1. type Method = 'GET' | 'POST'
  2. function request(url: string, method: Method) {}
  3. const options = {
  4. url: "ahcheng.top",
  5. method: "POST"
  6. }
  7. // options.method 会传参失败,因为 request 要求的是字面量类型 Method
  8. // 而 options.method 类型推导为 string
  9. request(options.url, options.method)
  10. // 三种解决办法
  11. // 1. 对 options.method 进行断言
  12. request(options.url, options.method as Method)
  13. // 2. 定义 options 对象时,就按类型要求定义
  14. type Request = {
  15. url: string,
  16. method: Method
  17. }
  18. const options: Request {
  19. url: "ahcheng.top",
  20. method: "POST" // method 类型不在为 string,而是 Method
  21. }
  22. // 3. 字面量类型推导添加 as const
  23. const options = {
  24. url: "ahcheng.top",
  25. method: "POST"
  26. } as const // 添加后,method 类型就推导为字面量类型 POST

类型缩小

什么是类型缩小呢?

  • 类型缩小的英文是 Type Narrowing;
  • 我们可以通过类似于 typeof padding === “number” 的判断语句,来改变TypeScript的执行路径;
  • 在给定的执行路径中,我们可以缩小比声明时更小的类型,这个过程称之为 缩小;
  • 而我们编写的 typeof padding === “number 可以称之为 类型保护(type guards);

常见的类型保护有如下几种:

  • typeof
    • 在 TypeScript 中,检查返回的值typeof是一种类型保护:因为 TypeScript 对如何typeof操作不同的值进行编码。
  • 平等缩小(比如===、!==)
    • 我们可以使用Switch或者相等的一些运算符来表达相等性(比如===, !==, ==, and != ):
  • instanceof
    • JavaScript 有一个运算符来检查一个值是否是另一个值的“实例”:
  • in
    • Javascript 有一个运算符,用于确定对象是否具有带名称的属性:in运算符
    • 如果指定的属性在指定的对象或其原型链中,则in 运算符返回true; ```javascript // 1.typeof的类型缩小 type IDType = number | string function printID(id: IDType) { if (typeof id === ‘string’) { console.log(id.toUpperCase()) } else { console.log(id) } }

// 2. 平等的类型缩小(=== == !== !=/switch) type Direction = “left” | “right” | “top” | “bottom” function printDirection(direction: Direction) { // 1.if判断 // if (direction === ‘left’) { // console.log(direction) // } else if ()

// 2.switch判断 // switch (direction) { // case ‘left’: // console.log(direction) // break; // case … // } }

// 3.instanceof function printTime(time: string | Date) { if (time instanceof Date) { console.log(time.toUTCString()) } else { console.log(time) } }

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)

// 4. in 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)

  1. <a name="Dbu0g"></a>
  2. # 函数类型的补充
  3. 在JavaScript开发中,函数是重要的组成部分,并且函数可以作为一等公民(可以作为参数,也可以作为返回值进<br />行传递)。<br />在定义函数的过程中,也可以给函数添加类型。我们可以编写函数类型的表达式(Function Type Expressions),来表示函数类型;
  4. ```javascript
  5. // 1.函数作为参数时, 在参数中编写类型
  6. function foo() {}
  7. type FooFnType = () => void // 函数类型:没有参数,没有返回值
  8. function bar(fn: FooFnType) {
  9. fn()
  10. }
  11. bar(foo)
  12. // 2.定义常量时, 编写函数的类型
  13. type AddFnType = (num1: number, num2: number) => number
  14. const add: AddFnType = (a1: number, a2: number) => {
  15. return a1 + a2
  16. }

在某些语言中,可能参数名称num1和num2是可以省略,但是TypeScript是不可以的:

参数的可选类型

我们可以指定某个参数是可选的,并且可选参数必须写在必传参数的后面。

  1. // 可选类型是必须写在必选类型的后面的
  2. // 可选类型其实就是和 undefined 的联合类型: y -> undefined | number
  3. function foo(x: number, y?: number) {
  4. }
  5. foo(20, 30)
  6. foo(20)

默认参数

从ES6开始,JavaScript是支持默认参数的,TypeScript也是支持默认参数的:

  1. // 参数顺序:必传参数 - 有默认值的参数 - 可选参数
  2. function foo(y: number, x: number = 20) {
  3. console.log(x, y)
  4. }
  5. foo(30)

剩余参数

从ES6开始,JavaScript 支持剩余参数,剩余参数语法允许我们将一个不定数量的参数放到一个数组中。剩余参数也是可选的。

  1. function sum(initalNum: number, ...nums: number[]) {
  2. let total = initalNum
  3. for (const num of nums) {
  4. total += num
  5. }
  6. return total
  7. }
  8. console.log(sum(20, 30))
  9. console.log(sum(20, 30, 40))
  10. console.log(sum(20, 30, 40, 50))

this

  1. // this是可以被推导出来 info对象(TypeScript推导出来)
  2. const info = {
  3. name: "why",
  4. eating() {
  5. console.log(this.name + " eating")
  6. }
  7. }
  8. info.eating()

但是对于某些情况来说,我们并不知道this到底是什么?

  1. function eating(message: string) {
  2. console.log(this.name + " eating", message);
  3. }
  4. const info = {
  5. name: "why",
  6. eating: eating,
  7. };
  8. info.eating("哈哈哈"); // 报错

这段代码运行会报错的:

  • 这里我们再次强调一下,TypeScript 进行类型检测的目的是让我们的代码更加的安全;
  • 所以这里对于 sayHello 的调用来说,我们虽然将其放到了info中,通过info去调用,this其实也是指向info对象的;
  • 但是对于TypeScript编译器来说,这个代码是非常不安全的,因为我们也有可能直接调用函数,或者通过别的对象来调用函数;

这个时候,通常TypeScript会要求我们明确的指定this的类型:

  1. type ThisType = { name: string };
  2. function eating(this: ThisType, message: string) {
  3. console.log(this.name + " eating", message);
  4. }
  5. const info = {
  6. name: "why",
  7. eating: eating,
  8. };
  9. // 隐式绑定
  10. info.eating("哈哈哈"); // why eating 哈哈哈
  11. // 显示绑定
  12. eating.call({name: "kobe"}, "呵呵呵") // kobe eating 呵呵呵
  13. eating.apply({name: "james"}, ["嘿嘿嘿"]) // james eating 嘿嘿嘿

函数的重载

在TypeScript中,如果我们编写了一个add函数,希望可以对字符串和数字类型进行相加,应该如何编写呢?
我们可能会使用联合类型来编写,但是联合类型不能直接使用 + ,需要类型缩小。

  1. function add(num1: string|number, num2: string|number) {
  2. // return num1 + num2 // 联合类型不能使用 + 运算符
  3. // 需要类型缩小
  4. if (typeof num1 === "string" && typeof num2 === "string") {
  5. return num1.length + num2.length
  6. } else if (typeof num1 === 'number' && typeof num2 === 'number') {
  7. return num1 + num2
  8. }
  9. }

通过联合类型有两个缺点:

  1. 进行很多的逻辑判断(类型缩小)
  2. 返回值的类型依然是不能确定,比如上面可能是字符串也可能是数字

为了实现这个需求,在TypeScript中,我们可以去编写不同的重载签名( overload signatures )来表示函数可以以不同的方式进行调用一般是编写两个或者以上的重载签名,再去编写一个通用的函数以及实现;
在我们调用add 函数的时候,它会根据我们传入的参数类型来决定执行哪一个重载签名,然后和实现函数的函数体组合执行。

  1. // 函数的重载: 函数的名称相同, 但是参数不同的几个函数, 就是函数的重载
  2. function add(num1: number, num2: number): number; // 没函数体
  3. function add(num1: string, num2: string): string;
  4. function add(num1: any, num2: any): any { // 通用函数的参数类型要写宽泛一点
  5. if (typeof num1 === 'string' && typeof num2 === 'string') {
  6. return num1.length + num2.length
  7. }
  8. return num1 + num2
  9. }
  10. const result = add(20, 30) // 走 number 类型的函数签名
  11. const result2 = add("abc", "cba") // 走 string 类型的函数签名
  12. console.log(result) // 50
  13. console.log(result2) // 6
  14. // 在函数的重载中, 实现函数是不能直接被调用的
  15. // add({name: "why"}, {age: 18})

当然一般情况下,函数重载太麻烦了,所以不需要类型缩小的时候,一般使用联合类型。