参考链接
1.1类型注解
let age:number = 18
说明:代码中的** :number** 就是类型注解
作用:为变量添加类型约束,上述声明的age 变量,只能赋值为number 类型
TS中的常用类型如下:
原始类型:number / string / boolean / null / undefined / symbol
对象类型:object(数组,对象,函数等对象)
TS新增类型 联合类型、自定义类型(类型别名)、接口、元组、字面量类型、枚举、void、any
1.2数组类型
// 推荐,简洁
let numbers:number[] = [1,2,3]
// 不推荐 ,麻烦,不直观
let strings:Array<string> = ['a','b','c']
1.3联合类型
需求:当数组中既有number类型,又有string类型,这个数组该如何写
let arr:(number | string)[] = [1,'a',2,'b']
解释:|(竖线)在TS中叫联合类型,(由两个或多个其他类型组成的类型,表示可以是这些类型中的任意一种,注意:这是TS中联合类型的语法,只有一根竖线,跟JS中的或 ( || ) 不同)
1.4类型别名
类型别名(自定义类型):为任意类型起别名
使用场景:当同一类型(复杂)被多次使用时,可以通过类型别名,简化该类型的使用
type CustomArray = (number | string)[]
let arr1:CustomArray = [1,'a',2,'b']
let arr2:CustomArray = ['x','y',6,7]
解释:
1. 使用type关键字来创建类型别名
2. 创建类型别名后,直接使用该类型别名作为变量的类型注解即可
1.5函数类型
单独指定参数和返回值类型 (推荐使用)
// 函数声明创建
function add(num1:number,num2:number):number{
return num1 + num2
}
// 函数表达式创建
const add = (num1:number,num2:number):number =>{
return num1 + num2
}
void 类型
如果函数没有返回值,那么,函数返回值类型就为:void
function greet(name:string):void{
console.log('hello',name)
}
函数可选参数
function mySlice(start?: number,end?: number):void{
console.log('起始索引:',start,'结束索引',end)
}
可选参数:在可传可不传的参数名称后面添加 ? (问号)
注意:可选参数只能出现在参数列表的最后,也就是说可选参数后面不能再出现必选参数
1.6对象类型
对象用法
let person:{name: string;age: number; sayHi(): void} = {
name:'jack',
age:19,
sayHi(){}
}
- 直接使用{}来描述对象结构,属性采用属性名:类型的形式;方法采用方法名():返回值类型的形式
- 如果方法有参数,就在方法名后面的小括号中指定参数类型(比如 greet(name:string):void )
- 再一行代码中指定对象的多个属性类型时,使用 ; (分号)来分割
- 如果一行代码只能指定一个属性类型(通过换行来分隔多个属性类型),可以去掉 ; (分号)
- 方法的类型也可以使用箭头函数形式(比如: {sayHi:() => void})
- 对象可选属性
function myAxios(config:{url:string;method?: string}){
console.log(config)
}
可选属性的语法与函数可选参数语法一致,都使用 ? (问号) 来表示
1.7接口
接口用法
一个对象类型被多次使用时,一般都会使用接口(interface)来描述对象的类型,达到复用的目的
interface Iperson{
name: string
age: number
sayHi(): void
}
let person: Iperson = {
name: 'jack',
age:19,
sayHi(){}
}
- 使用interface关键字来声明接口。
- 接口名称(比如,此处的Iperson),是可以任意合法的变量名称。
- 声明接口后,直接使用接口名称作为变量的类型。
- 因为每一行只有一个属性类型,因此,属性类型后没有 ; (分号)。
-
接口和类型别名的对比
相同点:都可以给对象指定类型
不同点:
接口,只能为对象指定类型
类型别名,不仅可以为对象指定类型,实际上可以为任意类型指定别名
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 Point2D extends Point2D { z: number}
1. 使用extends(继承)关键字实现了接口point3D继承point2D.
2. 继承后,Point3D就有了Point2D的所有属性和方法(此时,Point3D同时有x、y、z 三个属性)
1.8元组
使用场景:在地图中,使用经纬度来记录坐标信息
可以使用数组来记录坐标,那么该数组中只有两个元素,并且这两个元素都是数值类型
// 不严谨,因为该类型的数组中可以出现任意多个数值
let position: number[] = [39.5427,116.2317]
// 更好的方式:元组(Tuple)
let position:[number,number] = [39.5427,116.2317]
- 元组类型是另一种类型的数组,它确切的知道包含多少个元素,以及特定索引对应的类型
- 该示例中,元素有两个元素,每个元素的类型都是number
1.9字面量类型
let str1 = 'Hello TS'
const str2 = 'hello TS'
通过TS类型推论机制,可以得到答案
- 变量str1的类型为:string
- 变量str2的类型为:'Hello TS'
解释:
- str1是一个变量 ( let ) ,它的值可以是任意字符串,所以类型是:string
- str2 是一个常量( const ),它的值不能变化只能是'Hello TS',所以它的类型是:'Hello TS'
注意:此处的'Hello TS',就是一个字面量类型,也就是说某个特定的字符串也可以作为TS中的类型,除字符串以外,任意的JS字面量(比如,对象、数字等)都可以作为类型使用
使用场景:用于表示一组明确的可选值列表
使用模式:字面量类型配合联合类型一起使用
比如,再贪吃蛇游戏中,游戏的方向的可选值只能是上、下、左、右中的任意一个。
2.0枚举类型
枚举用法
// 声明枚举变量
enum Direction{Up,Down,Left,Right}
function changeDirection(direction:Direction){
console.log(direction);
}
//
changeDirection(Direction.Up)
枚举成员的值 数字枚举
枚举成员默认有值,默认从0开始自增的数值
如上图,从Up开始值,依次为 0 1 2 3
字符串枚举
enum DIrection{
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT'
}
注意:字符串枚举没有自增长行为,因此,字符串枚举的每个成员都必须有初始值 其他的类型仅仅被当作类型,而枚举不仅当作类型,还提供了值(所有的枚举类型都有值); 也就是说,其他的类型会在编译为JS代码时自动移除,但是,枚举类型会被编译为JS代码!
2.1 TS中的运算符-typeof
console.log(typeof 'Hello world')
// 打印 string
实际上,TS也提供了typeof操作符,可以在类型上下文中引用变量或属性的类型(类型查询)。 使用场景:根据已有的变量的值,获取该值的类型,来简化类型书写
解释:
- 使用typeof操作符来获取变量p的类型,结果与第一种(对象字面量形式的类型)相同
- typeof出现在类型注解的位置(参数名称的冒号后面)所处的环境就在类型上下文(区别于js代码)
- 注意:typeof只能用来查询变量或属性的类型,无法擦汗寻其他形式的类型(比如,函数调用的类型)
2.2 Typescript高级类型
2.1类的基本使用
解释:
- 根据TS中的类型推论,可以知道Person类的实例对象P的类型是Person
- TS中的class,不仅提供了class的语法功能,也作为一种类型存在
class Person{
age:number
gender = '男'
// gender: string = '男'
}
解释:
- 声明成员age,类型为number(没有初始值)
- 声明成员gender,并设置初始值,此时,可以省略类型注解(TS类型推论为string类型)
2.2类的构造函数
class Person{
age: number
gender: string
constructor(age: number,gender: string){
this.age = age
this.gender = gender
}
}
// 注意:此时,this后的变量指的是上面类里面的变量,
// 等号右侧的变量是形参传入的变量
解释:
- 成员初始化(比如,age:number)后,才可以通过this.age来访问实例成员
- 需要为构造函数指定类型注解,否则会被隐式推断为any;构造函数不需要返回值类型
2.3类的实例方法
class Point{
x = 1
y = 2
scale(n: number):void{
this.x *= n
this.y *= n
}
}
const p = new Point()
p.scale(10)
console.log(p.x,p.y)
// 输出 10 20
2.4类的继承的两种方式
class Animal{
move(){ console.log('Moving along!')}
}
class Dog extends Animal{
bark(){ console.log('汪!')}
}
const dog = new Dog()
解释:
- 通过extends关键字实现继承
- 子类Dog继承父类Animal,则Dog的实例对象dog就同时具有了父类Animal和子类Dog的所有属性和方法
2.4.1 implements(实现接口)
interface Singable{
sing():void
}
class Person implements Singable{
sing(){
console.log('Hello World')
}
}
解释:
1. 通过implements关键字让class实现接口
2. Person类实现接口Singable意味着,Person类中必须提供Singable接口指定的所有方法和属性
2.5类成员的可见性
// public
class Animal{
public move(){
console.log('Moving along!')
}
}
// protected
class Animal{
protected move(){console.log('Moving along!')}
}
class Dog extends Animal{
bark(){
console.log('汪汪汪!')
this.move() // 此处可用,因为是继承的子类
}
}
// private
class Animal{
private move(){console.log('Moving along!')}
walk(){
this.move()
}
}
解释:
- public(共有的),公有成员可以在任何地方被访问,默认可见性。
- protected(表示受保护的),仅对其声明所在类的子类中(非实例对象)可见,注意:所有通过实例的对象,均不可见
- private(私有的),只在当前类中可见,对实例对象以及子类都是不可见的
2.6类型兼容性
2.6.1 TS中的类型兼容性
class Point{x: number;y: number}
class Point2D{x: number; y: number}
const p: Point = new Point2D()
解释:
- Point和Point2D是两个名称不同的类
- 变量p的类型被显著的标为Point类型,但是它的值却是Point2D的实例,并没有类型错误
- 因为TS是结构化类型系统,只检查Point和Point2D的结构是否相同(相同,都具有x和y两个属性,属性类型也相同)
2.6.2对象的兼容性
class Point{x: number;y: number}
class Point3D{x: number;y: number;z: number}
const p:Point = new Point3D()
解释:
- 对于对象类型来说,y的成员至少与x相同,则x兼容y(成员多的可以赋值给少的)
- Point3D的成员至少与Point相同,则Point兼容Point3D
- 所以,成员多的Point3D可以赋值给成员少的Point
2.6.3 接口之间的类型兼容
interface Point{x: number;y: number}
interface Point2D{x: number;y: number}
let p1:Point
let p2:Point2D = p1
interface Point3D {x: number;y: number;z: number}
let p3:Point3D
p2 = p3
class Point3D {x: number;y: number;z: number}
let p3: Point2D = new Point3D()
解释:
- 接口之间的兼容性,类似于class
- class 和 interface之间也可以兼容
2.6.4 函数之间的兼容性
参数个数
type F1 = (a: number) => void
type F2 = (a: number,b: number) => void
let f1:F1
let f2:F2 = f1
// 参数多的兼容参数少的
// 参数少的可以赋值给参数多的
// 和上面的兼容性不同
参数类型
参数类型相同,相同位置的参数类型要相同(原始类型)或兼容(对象类型)
参数类型,相同位置的参数类型要相同或者兼容
解释:
- 注意,此处与前面讲到的接口兼容性冲突
- 技巧:将对象拆开,把每个属性看作一个个参数,则参数少的( f2 )可以赋值给参数多的(f3)
返回值类型
返回值类型,只关注返回值类型本身即可
解释:
- 如果返回值类型是原始类型给,此时两个类型都要相同,比如,左侧类型F5和F6
- 如果返回值类型是对象类型,此时成员多的可以赋值给成员少的,比如,右侧类型F7和F8
2.6.5 交叉类型
交叉类型(&):功能类似于接口继承(extends),用于组合多个类型为一个类型(常用于对象类型)
相当于
2.7 泛型和keyof
泛型的基本使用
function id<Type>(value:Type):Type{return value}
- 语法:在函数名称的后面添加<>(尖括号),尖括号中添加变量类型,比如此处的Type
- 类型变量Type,是一种特殊类型的变量,它处理类型而不是值。
- 该类型变量相当于一个容器,能够捕获用户提供的类型(具体是什么类型由用户调用该函数时指定)。
- 因为Type是类型,因此可以将其作为函数参数和返回值的类型,表示参数和返回值具有相同的类型。
- 类型变量Type,可以是任意合法的变量名称。
调用泛型函数
- 语法:在函数名称的后面添加<>尖括号,尖括号中制定具体的类型,
- 当传入类型number后,这个类型就会被函数声明时指定的类型变量Type捕获到。
- 此时,Type的类型就是number,所以,函数id参数和返回值的类型也都是number。 同样,如果传入类型string,函数id参数和返回值的类型就是string 这样,通过泛型就做到了让id函数与多种不同的类型在一起工作,实现了复用的同时保证了类型安全。
泛型约束
泛型约束:默认情况下,泛型函数的类型变量Type可以白标多个类型,这导致无法访问任何属性,比如,id('a')调用函数时获取参数的长度,Type可以代表任意类型,无法保证一定存在length属性,比如number类型就没有length,因此需要为泛型添加约束来收缩类型(缩窄类型取值范围)。
1.指定更加具体的类型
比如,将类型修改为Type[] (数组类型),因为只要是数组就一定存在length属性,因此就可以访问了
2.添加约束
-
创建描述约束的接口Ilength,该接口要求提供length属性。
-
通过extends关键字使用该接口,为泛型(类型变量)添加约束。
-
该约束表示:传入的类型必须有length属性。
多个泛型变量的情况
泛型的类型变量可以有多个,并且类型变量之间还可以约束(比如,第二个类型变量受第一个类型变量的约束)
- 添加了第二个类型变量Key,两个类型变量之间使用(,)逗号分割。
- keyof关键字接收一个对象类型,生成其键名称(可能是字符串或数字)的联合类型。
- 本实例中keyof Type实际上获取的是person对象所有键的联合类型,也就是:'name' | 'age'。
- 类型变量Key受Type约束,可以理解为Key只能是Type所有键中的任意一个,或者说只能访问对象中存在的属性
泛型接口
接口也可以配合泛型来使用,以增加其灵活性,增强其复用性。
- 在接口名称的后面添加<类型变量>,那么,这个接口就变成了泛型接口
- 接口的类型变量,对接口中所有的其他成员可见,也就是接口中所有成员都可以使用类型变量。
- 使用泛型接口是,需要显示指定具体的类型(比如,此处的IdFunc)。
- 此时,id方法的参数和返回值类型都是number;ids方法的返回值类型是number[]
数组是泛型接口
泛型类