概述
TypeScript是js的超集,ts提供了js的所有功能,并且额外的增加了: 类型系统
- 所有的js代码都是ts代码
- js有类型(number/string等),但是js不会检查变量的类型是否发生变化,而ts会检查
typescript类型系统的主要优势: 可以显示表基础代码中的意外行为,从而降低了发生错误的可能性
- 类型注解
- 常用基础类型
类型注解
示例代码:
let age: number = 18
说明: 代码中的 ' : number '就是类型注解
作用: 为变量添加类型约束,比如,上述代码中,约定变量 age 的类型为 number(数值类型)
解释: 约定了什么类型,就只能给变量赋值改类型的值,否则就会报错
常用基础类型概述
可以将ts中的常用基础类型细分为两类:
- js已有类型
- ts新增类型
js已有类型
- 原始类型: number/string/boolean/null/undefined/symbol
- 对象类型: object(包括: 数组, 对象, 函数等)
ts新增类型
- 联合类型
- 自定义类型(类型别名)
- 接口
- 元组
- 字面量类型
- 枚举
- void
- any
- 等等
原始类型
原始类型: number/string/boolean/null/undefined/symbol
特点: 简单,这些类型完全按照js中类型的名称来书写
let age: number = 18
let myName: string = 'zs'
let isLoading: boolean = false
数组类型(包括联合类型)
对象类型: object(包括,数组, 对象, 函数等)
特点: 对象类型,再ts中更加细化,每个具体的对象都有自己的类型语法
- 数组类型的两种写法: (推荐使用 number[] 写法)
let number: number[] = [1, 3, 5]
let string: Array<string> = ['a', 'b', 'c']
需求: 数组中既有number类型,又有string类型,可以用如下的方式
let arr: (number | string)[] = [1, 'a', 3, 'b']
解释: ' | '在ts中叫做联合类型(由两个或对各其他类型组成的类型,表示可以是这些类型中的任意一种)
注意: 这是ts中的联合类型的语法,只有一根竖线,需要与js中的或( || )进行区分
类型别名
类型别名(自定义类型): 为任意类型起别名
使用场景: 当同一类型(复杂)被多次使用是,可以通过类型别名,简化该类型的使用
type CustomArray = (number | string)[]
let arr1: CustomArray = [1, 'a', 3, 'b']
let arr2: CustomArray = ['x', 'y', 6, 7]
解释:
- 使用type关键字来创建类型别名
- 类型别名(比如上述 CustomArray),可以是人以合法的变量名称
- 创建类型别名后,直接使用该类型别名作为变量的类型注解即可
函数类型
函数的类型实际上指的是: 函数参数和返回值的类型
为函数指定类型的两种方式:
1.单独指定参数,返回值的类型
function add(num1: number, num2: number): number {
return num1 + num2
}
const add = (num1: number, num2: number): number => {
return num1 + num2
}
2.同时指定参数,返回值的类型
const add: (num1: number num2: number) => number = (num1, num2) => {
return num1 + num2
}
解释: 当函数作为表达式时,可以通过类似箭头函数形式的语法来为函数添加类型
注意: 这种形式只适用于函数表达式
3.void类型
如果函数没有返回值,那么函数的返回值类型为: void
function great(name: string): void {
console.log('Hello', name)
}
void是一个特殊的函数返回值类型
可选参数
使用函数实现某个功能时,参数可以传也可以不传,这种情况下,在给函数参数指定类型时,就用到可选参数了
比如,数组的slice()方法,可以slice()也可以slice(1),还可以slice(1, 3)
function mySlice(start?: number, end?: number): void {
console.log('起始索引: ', start, '结束索引: ', end)
}
在进行打印的时候,未填的可选参数展示undefined
可选参数: 可传可不穿的参数名称跟后面添加 ' ? '(问号)
注意: 可选参数只能出现在参数列表的最后,也就是说可选参数后面不能再出现必选参数
对象类型
js中的对象是由属性和方法构成的,而ts中对象的类型就是在描述对象的结构(有什么类型的属性和方法)
对象类型的写法:
let person: { name: string; age: number; sayHi(): void; greet(name: string): void } = {
name: 'zs',
age: 18,
sayHi() {},
greet(name) {}
}
解释:
-
直接使用 {} 来描述对象结构,属性采用属性名: 类型的形式;方法采用方法名(): 返回值类型的形式
-
如果方法有参数,九在方法名后面的小括号中指定参数类型(比如: greet(name: string): void)
-
在一行代码中指定对象的多个属性类型时,使用 ' ; ' (分号)来分隔
- 如果一行代码中至指定一个属性类型(通过换行来分隔多个属性类型),可以去掉 ' ; '(分号),如下
- 方法的类型也可以使用箭头函数形式(比如: { sayHi: () => void })
let person: {
name: string
age: number
// sayHi(): void
sayHi: () => void
greet(name: string): void
} = {
name: 'zs',
age: 18,
sayHi() {},
greet(name) {}
}
可选属性
对象的属性或方法,也是可选的
比如,在使用axios({...})时,如果发送GET请求,method属性就可以省略
function myAxios(config: { url: string; method?: string }) {
console.log(config)
}
可选属性的语法与函数可选参数的语法一致,都使用 ' ? '(问号)来表示
接口
当一个对象类型被多次使用时,一般会使用接口来描述对象的类型,达到复用的目的
// let person: { name: string; age: number; sayHi(): void } = {
// name: 'zs',
// age: 18,
// sayHi() {}
// }
// let person1: { name: string; age: number; sayHi(): void }
interface IPerson {
name: string
age: number
sayHi(): void
}
let person2: IPerson = {
name: 'zs',
age: 18,
sayHi() {}
}
解释:
- 使用 interface 关键字来声明接口
- 接口名称(比如,IPerson),可以是任意合法的变量名称
- 声明接口后,直接使用接口名称作为变量的类型
- 因为每一行只有一个属性类型,因此,属性类型后没有 ' ; '(分号)
接口和类型别名(对比)
interface(接口)和type(类型别名)的对比:
- 相同点: 都可以给对象指定类型
- 不同点:
- 接口,只能为对象指定类型
- 类型别名,不仅可以为对象指定类型,实际上可以为任何类型指定别名
// 接口
interface IPerson {
name: string,
age: number,
sayHi(): void
}
// 类型别名
type IPerson = {
name: string
age: number
sayHi(): void
}
// 类型别名
type NumStr = number | string
接口的继承
如果两个接口之间有相同的属性或方法,可以将公共的属性或方法抽离出来,通过继承来实现复用
比如,这两个接口都有x, y两个属性,重复写两次比较繁琐
interface Point2D { x:number; y: number }
interface Point3D { x:number; y: number; z: number }
更好的方式:
interface Point2D { x: number; y: number }
interface Point3D extends Point2D { z: number }
解释:
- 使用 extends (继承) 关键字实现了接口 Point3D 继承 Point2D
- 继承后, Point3D 就有了 Point2D 的所有属性和方法 (此时,Point3D 同时有 x, y, z 这三个属性)
interface Point2D { x: number; y: number }
// 使用 继承 实现复用
interface Point3D extends Point2D { z: number }
let p3: Point3D = {
x: 1,
y: 0,
z: 2
}
元组
场景: 在地图中,使用经纬度坐标来标记位置信息
可以使用数组来记录坐标,那么,该数组中只有两个元素,并且这两个元素都是数值类型
let position: number[] = [39.5427, 116.2317]
使用 number[] 的缺点: 不严谨,因为改类型的数组中可以出现任意多个数字
更好的方式: 元组(Tuple)
元组类型时另一种类型的数组,他确切的知道包含多少个元素,以及特定索引对应的类型
let position: [number, number] = [39.5427, 116.2317]
解释:
- 元组类型可以确切的标记处有多少个元素,以及每个元素的类型
- 示例中,元素有两个元素,每个元素的类型都是number
类型注解
在TS中,某些没有明确指出类型的地方,TS的类型推论机制会帮助提供类型
发生类型推论的2中常见场景:
- 声明变量并初始化时
- 决定函数返回值时
// 注意: 如果声明变量但是没有立即初始化值 即赋值 必须手动添加类型注解
let age = 18
function add(num1: number, num2: number) {
return num1 + num2
}
注意: 这两种情况下,类型注解可以省略不写
推荐: 能省略类型注解的地方就省略,提升开发效率,建议参数添加上类型注解
技巧: 如果不知道类型,可以通过鼠标放在变量名上,通过编译器提示查看类型
类型断言
有时候开发者比ts更明确一个值的类型,此时可以使用类型断言来指定更具体的类型
比如
<a href="http://www.itcast.cn/" id="link">传智教育</a>
注意: getElementById 方法返回值的类型时 HTMLElement,改类型只包含所有标签公共的属性或方法,不包含 a 标签特有的 href 等属性
因此,这个类型太宽泛(不具体),无法操作 href 等 a 标签特有的属性或方法
解决方法: 这种情况下就需要使用类型断言指定更加具体的类型
使用类型断言:
const aLink = document.getElementById('link') as HTMLAnchorElement
解释:
- 使用 as 关键字实现类型断言
- 关键字 as 后面的类型是一个更加具体的类型( HTMLAnchorElement 是 HTMLElement 的子类型 )
- 通过类型断言,aLink的类型变得更加具体,这样就可以访问 a 标签特有的属性或方法了
另一种语法,使用 <> 语法,这种语法形式不常用:
const aLink = <HTMLAnchorElement>document.getElementById('link')
技巧: 在浏览器控制台,通过 console.dir() 打印 DOM 元素,在属性列表的最后面,即可看到该元素的类型
在浏览器中选中一个元素,标签后面显示 $0
去到控制台,输入
console.dir($0)
展开后在最底部即可看到元素类型
字面量类型
let str1 = 'Hello TS'
const str2 = 'Hello TS'
通过TS类型推论机制,就可以得到答案:
- 变量 str1 的类型为: string
- 变量 str2 的类型为: 'Hello TS'