🔥keyipatience:个人主页
🎬作者简介:C/C++后端开发学习者
🌟专栏传送门:《c++》《linux》《c++高阶数据结构》《c++数据结构与算法》《前端学习》
⭐️patience is key in life
TypeScript 是什么
- TypeScript(简称:TS)是 JavaScript 的超集(JS 有的 TS 都有)。
- TypeScript =Type+ JavaScript(在 JS 基础之上,为 JS 添加了类型支持)。
- TypeScript 是微软开发的开源编程语言,可以在任何运行 JavaScript 的地方运行。
简单理解
- 超集:所有合法的 JS 代码,都是合法的 TS 代码;TS 在 JS 基础上新增语法。
- 核心特点:给 JS 加上静态类型,写代码时就能检查类型错误,而不是等到运行时报错。
- 运行原理:TS 不能直接在浏览器 / Node 执行,需要编译转换成普通 JS再运行。
TypeScript 为什么要为 JS 添加类型支持?
背景
JS 的类型系统存在先天缺陷,JS 代码里绝大多数错误都是类型错误(Uncaught TypeError)
后果:增加找 Bug、改 Bug 的时间,降低开发效率
静态类型 vs 动态类型
- TS:静态类型语言→编译期做类型检查
- JS:动态类型语言→执行期做类型检查
代码流程顺序:1 编译 → 2 执行
- JS:代码运行起来之后,才能发现类型错误(发现得晚)
- TS:编译阶段(执行代码之前)就能捕获错误;搭配 VSCode,写代码的时候就提示报错
- 收益:提前发现问题,减少排查、修复 bug 的时间
对比
- JS:边跑边检查,出错在运行时
- TS:写代码 / 编译时就检查,提前拦截类型错误
eg:
// JS:不会报错,直到运行才崩 let num = 100 num.toUpperCase() // 运行时报 Uncaught TypeError,代码写完看不出问题// TS:写代码时就标红提示错误 let num:number = 100 num.toUpperCase() // 还没运行,编辑器直接提示:数字没有toUpperCase方法TypeScript 相比 JS 的优势
- 更早发现错误:写代码的时候就能捕获问题,减少排查、修复 bug 的时间,提升开发效率。
- 完善代码提示:代码任意位置都有智能提示,开发更安心,提升开发体验。
- 代码可维护性强:类型系统让重构代码更简单,适合大型项目。
- 支持最新 ES 语法:可以优先使用 ECMAScript 新特性。
- 类型推断:不用每一处都手动写类型标注,在获得类型好处的同时降低编码成本。
框架生态:Vue3 源码由 TS 重写,Angular 原生默认 TS,React 和 TS 可以完美配合。TS 已经是大中型前端项目首选语言。
类型推论
两个最常见场景
1. 声明变量并赋值
let age = 18TS 看到18,自动推断age是number类型,等价于
let age: number = 18这里: number可以省略
2. 函数返回值推断
function add(num1: number, num2: number) { return num1 + num2 }TS 发现 return 返回两个数字相加,自动推断返回值是 number,等价于:
function add(num1: number, num2: number): number { return num1 + num2 }返回值: number可以省略
核心规则
- 变量只声明、不赋值,TS 无法推断,此时必须手动标注类型
let a; // 推断为 any(不推荐) let b: number; // 没有初始化,必须写类型推荐原则:能让 TS 自动推断,就不手动写类型,代码更简洁
技巧:如果不知道类型,可以通过鼠标放到变量名称上,用vscode的提示看类型
类型注解
因为有类型推断,所以复杂场景才需要手动写类型注解
let age: number = 18: number这部分就是类型注解
- 作用:给变量增加类型约束。这里约定变量
age只能是number数值类型。 - 规则:约定好类型之后,只能赋值同类型数据,类型不匹配就直接报错
错误示例
let age: number = '18'报错信息:不能将类型
string分配给类型number,ts (2322) 解析:'18'是字符串,和约定的 number 类型冲突,编辑器直接标红。
常用基础类型
TS 基础类型分为两大类:JS 已有类型、TS 新增类型
1. JS 已有类型
- 原始类型:
number/string/boolean/null/undefined/symbol - 对象类型:
object(包含数组、普通对象、函数等)
let age: number = 18 let myName: string = '刘老师' let isLoading: boolean = false // null、undefined、symbol写法同理 let n: null = null let u: undefined = undefined let s: symbol = Symbol() //每次调用Symbol()生成的值,永远是唯一的。2. TS 新增类型
联合类型、自定义类型(类型别名)、接口、元组、字面量类型、枚举、void、any等
数组类型
数组两种写法
// 写法1:简写,推荐 let numbers: number[] = [1, 3, 5] // 写法2:泛型写法 Array<类型> let strings: Array<string> = ['a', 'b', 'c']数组包含多种类型(联合类型|)
需求:数组里同时有number和string
let arr: (number | string)[] = [1, 'a', 3, 'b']|是联合类型:代表可以是竖线两边任意一种类型- 注意:TS 联合是单竖线
|,不要和 JS 逻辑或||搞混 - 括号不能少:
(number | string)[]代表数组的成员可以是数字或字符串
易错对比:
number | string[]→ 含义是:要么是一个数字,要么是字符串数组(不是我们想要的)
类型别名
类型别名(自定义类型):给任意类型起一个简短的别名。
适用场景:复杂类型需要重复写很多次的时候,用别名简化代码
// type 关键字定义别名 type CustomArray = (number | string)[] // 直接使用别名当做类型 let arr1: CustomArray = [1, 'a', 3, 'b'] let arr2: CustomArray = ['x', 'y', 6, 7]函数类型
函数类型 =参数类型 + 返回值类型两种写法:
- 直接给参数、返回值标注类型
- 一次性整体定义函数类型
写法 1:普通 function 声明
function add(num1: number, num2: number): number { return num1 + num2 }(num1: number, num2: number):标记参数类型- 括号后面的
: number标注返回值类型 - 这里没有类型描述用的
=>,就是普通 js 函数
写法 2:箭头函数
const add = (num1: number, num2: number): number => { return num1 + num2 }(num1: number, num2: number):标记参数类型- 括号后
: number标记返回值类型 =>:JS 箭头函数本身的语法,代表函数体开始
写法3:整体标注函数类型
const add: (num1: number, num2: number) => number = (num1, num2) => { return num1 + num2 }const add:冒号:的意思:定义变量 add 的类型
(num1: number, num2: number) => number这一段就是add 的类型
num1: number:参数 1 是数字,num2: number:参数 2 是数字=> number:意思:这个函数接收上面两个参数,返回数字这是写类型,所以用=>,不能写:number
=右边:函数真正的代码(函数实现,干活的)
(num1, num2) => { return num1 + num2 }
- 这里不用写参数类型!TS 会看左边的说明书,自动推断 num1、num2 都是 number
- 这是 JS 原生箭头
=>,代表函数体开始
注意:
:是给「变量 / 参数」标记类型
(参数) => 返回值是 TS 专门用来描述【函数类型】的固定模板
// 1. 变量是数字(普通类型,用 :) let x: number // 2. 变量是函数(函数类型,用 =>) let fn: () => number //() => number描述fn的类型 // fn是函数,无参数,返回number。这里不能写成 ():number如果函数没有 return,返回类型写void
function logMsg(msg:string): void { console.log(msg) }可选参数
function mySlice(start?: number, end?: number): void { console.log('起始索引:', start, '结束索引:', end) }- 语法:参数名后面加
?→ 代表这个参数可传、可不传start?: number等价于start: number | undefined
- 硬性要求:可选参数必须放最后,可选后面不能跟必选参数
// ❌ 错误!可选参数后面有必选 function fn(a?:number, b:number){} // ✅ 正确,必选在前,可选在后 function fn(a:number, b?:number){}调用示例
mySlice() // 不传,start=undefined, end=undefined mySlice(1) // 只传start mySlice(1,3) // 两个都传默认参数
function mySlice(start: number = 0, end?: number): void { console.log('起始索引:', start, '结束索引:', end) }参数后面直接写= 默认值start: number = 0含义:调用时不传这个参数,start 自动等于
带默认值的参数,也建议放后面
mySlice() // start=0,end=undefined mySlice(2) // start=2,end=undefined mySlice(2,5) // start=2,end=5可选参数?的区别
start?: number:不传 →undefinedstart = 0:不传 → 自动赋值 0,不用写类型,TS 会自动推断
剩余参数
把多个剩余的参数打包成数组,用...。
...xxx: 类型[],收集剩下所有参数,只能放末尾
function sum(a:number, ...others: number[]){ console.log(a, others) } sum(1,2,3,4) // a=1,others=[2,3,4]TS 对象类型
TS 对象类型:描述对象长什么样子(有哪些属性、哪些方法,各自是什么类型)
写法规则
1.TS 在对象类型的大括号内,逗号,、分号;、换行不写分隔符,三种都支持,效果完全一样。
let person: { name: string; age: number; sayHi(): void } = { name: 'jack', age: 19, sayHi() {} } // 逗号分隔合法 let person: { name: string, age: number, sayHi(): void } = { name: 'jack', age: 19, sayHi() {} } // 直接不写 let person: { name: string age: number sayHi(): void } = { name: 'jack', age: 19, sayHi() {} } // 1. 逗号 { name: string, age: number, sayHi(): void } // 2. 分号 ; { name: string; age: number; sayHi(): void } // 3. 换行,什么都不写 { name: string age: number sayHi(): void }2.属性:属性名: 类型
name:string→ name 必须是字符串age:number→ age 必须是数字
3.函数方法的两种写法(等价)
// 写法1 sayHi(): void 仿普通函数声明的语法 - sayHi:方法名 - ():无参数 - : void`:返回值类型 // 写法2 箭头函数形式(TS推荐) sayHi: () => void 把它看成属性的类型标注 - sayHi: → sayHi 是一个属性 - () => void → 这个属性的类型是「一个无参、返回 void 的函数」TS 对象可选属性
function myAxios(config: { url: string; method?: string }) { console.log(config) }function myAxios:是函数,是我们定义的函数本身config:是传给这个函数的参数,这个参数的类型是对象类型{ url:string; method?:string }method?: string:是对象里面的属性,叫对象可选属性
拆开看
1.myAxios是函数,调用的时候,要传一个对象进去
// 传入的 {} 就是对象 myAxios({ url: '/api' })2.这个传入的对象,结构要求:必须有url(必选属性)method可有可无(对象的可选属性)
重点:
?不是加在函数参数上!
- 如果是函数可选参数长这样:
function fn(a?: number){}a整个参数可传可不传- 现在是:
config: { method?: string }
config这个参数必须传(不能不传 config)
// ① 函数【可选参数】:整个参数config可以不传 function myAxios(config?: { url:string, method:string }){} myAxios() // ✅ 可以直接不传config // ② 对象【可选属性】:config必须传,只是对象内部的method属性可以没有 function myAxios(config: { url:string, method?: string }){} myAxios() // ❌ 报错!必须传config对象 myAxios({url:"/api"}) // ✅ 传了config对象,对象里省略methodinterface 接口
一句话:接口就是把前面写在{}里的对象类型单独抽出来,起个名字,方便重复使用
// 定义接口 IPerson,描述对象结构 interface IPerson { name: string age: number sayHi(): void } // 直接用 IPerson 当做类型 let person: IPerson = { name: 'jack', age: 19, sayHi() {} }和直接写对象字面量类型对比
// 原来的写法:每次都要把一长串{}写一遍 let person: { name:string; age:number; sayHi():void } = {...} // 接口写法:定义一次IPerson,到处复用 interface IPerson { name: string age: number sayHi(): void } let p1: IPerson = {...} let p2: IPerson = {...} let p3: IPerson = {...}核心目的:复用对象类型,不用重复写一大串{...}
interface 和 type(类型别名)对比
相同点
都能描述对象的结构,下面两段代码效果完全一样:
// interface 写法 interface IPerson { name: string age: number sayHi(): void } // type 类型别名写法 type IPerson = { name: string age: number sayHi(): void }不同点
interface接口- 只能用来描述对象(普通对象、函数对象、类)
- 不能给基础类型、联合类型起别名
- 特点:支持自动合并声明、extends 继承扩展(后面马上讲)
type类型别名- 万能:可以给任意类型起别名,不限于对象
- ✅ 对象
- ✅ 基础类型
type Num = number - ✅ 联合类型
type NumStr = number | string
// type能干,interface做不到的例子 type NumStr = number | string let a: NumStr = 10 let b: NumStr = 'abc' // type 给基础类型起别名 type MyStr = string let s: MyStr = 'hello'接口继承extends
核心一句话:A extends B,代表接口 A 自动拥有接口 B 里面全部属性,再加上自己新增的属性,复用公共代码,避免重复抄写
// 父接口:二维点 interface Point2D { x: number; y: number } // 子接口 Point3D 继承 Point2D interface Point3D extends Point2D {z: number}继承完成后,Point3D 等价于同时拥有 x,y,z 三个属性
调用示例
let p: Point3D = { x:10, y:20, z:30 }可以多继承(一个接口同时继承多个接口)
interface A {a:number} interface B {b:number} interface C extends A,B {c:number} // C 拥有 a,b,c