文章目录
- TypeScript入门
- 2.编译并运行TS代码
- 2.1.简化运行ts步骤
- 3.TS中的常用类型
- 3.1.TS中的类型注解
- 3.2.TS中的原始类型
- 3.3.TS中的数组类型
- 3.4.TS中的联合类型
- 3.5.类型别名
- 3.6.函数类型
- 3.6.1.单独执行参数、返回值类型
- 3.6.2.同时指定参数,返回值类型
- 3.6.3.函数的void类型
- 3.6.4.函数可选参数*
- 3.7.TS中的对象类型
- 3.8.接口
- 3.9.元组
- 3.10.类型推论
- 3.11.函数类型断言
- 3.12.字面量类型
- 3.13.枚举
- 3.14.any类型
- 3.15.typeof
- 4.TS中的高级类型
- 4.1.class类
- 4.2.class的构造函数
- 4.3.class类的实例方法
- 4.4.class继承
- extends
- implements
- 4.5.类的可见性修饰符
- 公有的(public)
- 受保护的(protected)
- 私有的(private)
- 4.6.readonly只读属性
- 4.7.类型兼容性
- 4.8.对象之间的类型兼容性
- 4.9.接口之间的兼容性
- 4.10.函数之间的兼容性
- 参数个数
- 函数参数
- 返回值
- 返回值是原始类型
- 返回值是对象类型
- 4.3.交叉类型
- 4.4.交叉类型 和 接口继承 之间的比对
- 4.5.*泛型
- 创建泛型函数
- 调用泛型函数
- 简化函数的调用
- 泛型约束
- 指定更加具体的类型
- **添加约束**
- *多个泛型变量情况
- 泛型接口
- 数组是泛型接口
- 泛型类
- 泛型工具类
- Patial
- Readonly
- Pick
- Record
- 4.6.索引签名类型
- 4.7.映射类型
- 4.8.映射类型(keyof)
- 4.9.索引查询类型
- 查询单个
- 查询多个
- 5.**类型声明文件
- TS中的两种文件类型
- 类型声明文件的使用说明
- 5.1.创建自己的类型声明文件
- 项目内共享类型
- 为已有的JS文件提供类型声明
- declare关键字
TypeScript入门
2.编译并运行TS代码
-
创建hello.ts文件
-
const info : string = 'hello ts'; console.log(info)
-
-
将TS文件编译为JS文件,在终端中输入命令 tsc .\hello.ts (此时该目录下会生成一个同名的JS文件)
-
执行JS代码,在终端中输入命令 node hello.js (即可执行刚刚的js文件)
需要注意的是,TS编译生成的JS文件中,代码中就没有类型的信息了。
2.1.简化运行ts步骤
简化方式 通过ts-node包,可以直接在node中运行ts代码,不用每次再使用ts进行编译 在使用node进行运行
npm i -g ts-node
# 使用 就可以实现编译运行两步操作
# 注意 ts-node 并没有生成js文件,他在内部偷偷帮你转换,并且运行
ts-node hello.ts
3.TS中的常用类型
在TypeScript(TS)中,类型系统是其核心特性之一,为JavaScript增添了静态类型检查的能力,从而提高代码的可维护性和安全性。
也可以将TS中测常用基础类型细分为两类:
- JS已有类型
- 原始类型:number/string/boolean/null/undefined/symbol
- 对象类型:object(包括 数组、对象、函数等对象)
- TS新增类型
- 联合类型、自定义类型、接口、元组、字面量类型、枚举、void、any等
以下是TypeScript中一些常用且重要的类型:
基本类型:
string
:用于文本字符串。number
:用于数值,包括整数和浮点数。boolean
:布尔值,只能是true
或false
。null
和undefined
:表示空值或未定义的值,TypeScript 中它们是所有类型的子类型。void
:表示没有任何返回值的函数。never
:表示永远不会出现的值的类型,常用于抛出异常或无限循环的函数。数组类型:
- 使用
Array<元素类型>
或元素类型[]
定义,例如number[]
表示一个数字数组。元组(Tuple):
- 定义固定长度和类型的数组,例如
[string, number]
表示一个数组,其第一个元素为字符串,第二个元素为数字。对象类型(Object):
使用接口(
interface
)或类型别名(type
)来描述对象结构,如:interface Person { name: string; age: number; }
枚举(Enum):
用于定义一组命名的常量,如:
1enum Color {Red, Green, Blue}
任意类型(any):
- 表示可以是任何类型,使用时需谨慎,因为它绕过了类型检查。
联合类型(Union Types):
- 使用管道符号
|
分隔,表示一个值可以是多种类型中的一种,如string | number
。类型断言(Type Assertion):
- 用来告诉编译器某个值的类型,形式为
value as Type
。字面量类型(Literal Types):
- 直接使用具体的值作为类型,如
const answer: 42 = 42;
。索引签名(Index Signatures):
- 用于定义对象中动态属性的类型,如
{ [key: string]: any }
。类(Class)和接口:
- 类用于创建对象的蓝图,接口用于定义类或对象的形状。
泛型(Generics):
- 提供了编写可重用组件的方式,这些组件可在多种数据类型上工作,如
Array<T>
。
3.1.TS中的类型注解
示例代码:
// 只需要在 变量后面 : 具体的类型 即可
let age : number = 19
代码中的 number
类型就是类型的注解,作用就是为变量添加类型约束
,比如上面为age类型添加了 number
类型的约束(数值类型),一旦约定了什么类型,就只能给变量赋值什么类型,否则就会报错
3.2.TS中的原始类型
原始类型:number/string/boolean/null/undefined/symbol
这些类型,完全按照JS中类型的名称来书写即可,非常简单。
/* number/string/boolean/null/undefined/symbol */
let age : number = 30
let username : string = '张三'
let isRunning : boolean = true
console.log("年龄:",age)
console.log("姓名:",username)
console.log("是否在奔跑:",isRunning)
3.3.TS中的数组类型
对象类型:object(包括,数组、对象、函数等对象)。
对象类型在TS中更加细化,每个具体的对象都有自己类型的语法
数组类型的写法
推荐使用 number[]
这种写法
let numbers : number[] = [1,3,3,4,5,6,7,8,9,10]
let strings : Array<string> = ['1','2','3','4','5','6','7','8','9','10']
console.log(numbers)
console.log(strings)
3.4.TS中的联合类型
当数组中既有number类型又有string类型,这个数组的类型该如何书写的?
如果数组中既有number类型 又有string类型,这时候需要使用 | ts中的联合类型(由两个或者多个类型组成类型,表示可以是这些类型中的任意一种),主要这里只是 | 一个竖线,不是两个 两个 || 是逻辑表达式
let listInfo : (string | number | boolean)[] = ['1',2,'3',4,true]
// 打印当前集合数据 以及类型
listInfo.forEach(item=>{
console.log(item + '\t\t' + typeof(item))
})
3.5.类型别名
类型别名(自定义类型):为任意类型起别名
当一个类型(复杂)并且多次被使用时,可以通过类型别名,简化该类型的使用。
// 类型别名
type CustomArray = (number | string | boolean)[]
let user1 : CustomArray = ['张三',21,true]
user1.forEach(item=>{
console.log(item +"\t" + typeof(item))
})
- 使用
type 关键字
来创建类型的别名。 - 类型别名,可以是
任意合法的变量名称
。 - 创建类型别名后,直接使用该类型别名作为变量的类型注解即可。
3.6.函数类型
函数的类型实际上指的是:
函数的参数
和函数的返回值
类型为函数指定类型的两种方式
- 单独指定参数、返回值类型。
- 同时执行参数、返回值类型。
3.6.1.单独执行参数、返回值类型
// 函数表达式形式
const add = (num1: number, num2: number): number => {
return num1 + num2;
}
const res = add(1, 2)
console.log("最终计算结果:" + res + '\t' + typeof (res) + '\t')
3.6.2.同时指定参数,返回值类型
const addNum : (num1:number, num2:number)=> number = (num1,num2)=>{
return num1 + num2;
}
const res = addNum(1,3)
console.log("最终计算结果:" + res + '\t' + typeof (res) + '\t')
当函数作为表达式时,可以通过 类似箭头函数形式的语法
来为函数添加类型
注意:这种形式只适用于函数表达式
3.6.3.函数的void类型
如果函数没有返回值,那么函数的返回值类型就为🐤
void
const getUserName = (name: string): void => {
console.log(name)
}
getUserName('迪加!')
3.6.4.函数可选参数*
当使用函数实现某个功能时,参数可以传,也可以不传。这种情况下,在给函数参数指定类型时,就用到
可选参数
了。比如数组中的
slice方法
可以使用slice()
也可以使用slice(1)
也可以使用slice(1,3)
const mySlice = (start?: number, end?: number): void => {
console.log("起始索引:" + start + "\t" + "结束索引:" + end)
}
// 使用了可选参数,那么我们自定义的 mySlice()中的参数 可以传 也可以不传了
mySlice()
mySlice(1)
mySlice(1, 3)
可选参数:在可传 可不传的参数后面添加?(问号)
注意:可选参数只能出现在参数列表的最后,可选参数后面不能再出现参数
3.7.TS中的对象类型
JS中的对象是由属性和方法构成的,而TS中对象的类型就是在描述对象的结构(有什么属性 和 方法)
对象类型的写法:
// 单行形式
let person2: { name: string; age: number; show(): void; } =
{
name: '张三',
age: 19,
show() {
console.log('你好,我是' + this.name + '\t' + '我今年' + this.age + '岁了');
}
}
// 多行形式
let person: {
name: string,
age: number,
show(): void
}= {
name: '张三',
age: 19,
show() {
console.log('你好,我是' + this.name + '\t' + '我今年' + this.age + '岁了');
}
}
person.show()
- 直接使用{}来描述对象结构,属性采用
属性名:类型
的形式,方法采用方法名():返回值类型
的形式 - 如果方法有参数,就在方法名称后面的小括号中指定类型参数 例如
show(name:string):void
- 在一行代码中指定对象的多个属性类型时,使用
;(分号)
来分隔- 如果一行代码只能指定一个属性类型,(通过换行来分割多个属性类型,可以去掉
;(分号)
) - 方法的类型可以使用箭头函数的形式(比如
{show:()=>void}
)
- 如果一行代码只能指定一个属性类型,(通过换行来分割多个属性类型,可以去掉
TS对象中的可选属性
对象的属性或者方法也是可选的,此时就需要用到可选属性
比如我们在使用axios({ … })时,如果发送GET请求,mothod属性就可以省略
// 如果我们不传methods 那么默认的请求方式就是get
// 可选属性的语法 与 函数的可选参数语法一直 使用 ? 来表示
const myAxios = (config: { url: string, method?: string }): void => {
console.log(config);
}
myAxios({ url: "http://localhost:9000/api/v1/test" })
myAxios({ url: "http://localhost:9000/api/v1/test", method: 'POST' })
3.8.接口
当一个对象类型被多次使用的时候,一般会使用
接口(interface)
来描述对象的类型,达到复用的目的
/**
* 定义接口
*/
interface IPerson {
name: string,
age: number,
printInfo(): void
}
let personInfo: IPerson = {
name: '张三',
age: 22,
printInfo() {
console.log('姓名:' + this.name + '年龄:' + this.age);
}
}
personInfo.printInfo()
- 使用
interface
关键字来声明接口 - 接口名称,可以是任意合法的变量名称
- 声明接口后,直接使用
接口名称作为变量的类型
- 因为每一行只有一个类型属性,因此属性后面没有
;(分号)
接口 和 类型别名的对比
- 相同点:都可以给对象指定类型
- 不同点:
- 接口,只能为对象指定类型
- 类型别名,不仅可以为对象指定类型,实际上可以为任意类型指定别名
/**
* 定义接口
*/
interface IPerson {
name: string,
age: number,
printInfo(): void
}
/**
* 定义类型 注意:这里后面有一个 = 相当于把对象的结构赋值给TPerson
*/
type TPerson = {
name: string,
age: number,
printInfo(): 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所有的属性和方法
let point_info:Point3D ={
x:1,
y:2,
z:3
}
console.log(point_info);
3.9.元组
在地图中,经常使用经纬度坐标来标记位置信息
可以使用数组来记录坐标,那么,该数组的中只有两个元素,并且这两个元素都是数值类型
let position: number[] = [39.3232, 116.1232]
使用number[]的缺点:不够严谨,因为该类型的数组中可以出现任意多个数字。
更好的方法,可以使用元组(Tuple)
元组类型是另一种类型的数组,它准确的直到了包含了多个元素,以及特定索引位置的对应类型
let position: [number, number, boolean] = [39.3232, 116.1232, true]
- 元组类型可以确切的
标记出有多少个元素
,以及每个元素的类型
- 该示例中,元素有3个元素,前两个元素的类型是number,第三个元素的类型是boolean
3.10.类型推论
在TS中,某些没有明确指出类型的地方,TS的类型推断机制就会帮助提供类型。
发生类型推论的两种常见场景:
- 声明变量并初始化时
- 决定函数返回值时
// 声明变量时
let age = 12
// 决定函数返回类型时
const addNumer = (num1: number, num2: number) => {
return num1 + num2;
}
这两种情况下,类型注解可以进行省略不写!
推荐:能省略的类型注解的地方就省略,充分利用ts类型推论的能力(偷懒)
3.11.函数类型断言
在开发的过程中,有的时候我们比TS更加能明确一个值的类型,可以使用
类型断言
来指定更具体的类型
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<a href="wwww.baidu.com" id="link">跳转到百度网址</a>
</body>
</html>
<script>
const linked = document.getElementById('link')
</script>
注意:getElementById方法返回值的类型时HTMLElement,该类型只包含了所有标签的公共的属性或者方法,不包含a标签特有的href等属性
因此,这个类型不太具体,无法操作href等a标签特有的属性 和 方法
解决方式:这种情况下就需要使用类型断言指定更加具体的类型
as关键字
<script type="text/typescript">
// 如果我们指定的HTMLElement 是无法获取的href等属性的
const linked = document.getElementById('link') as HTMLAnchorElement
</script>
- 使用
as关键字
实现类型断言
- 关键字as后面的类型是一个更加
具体的类型
,HTMLAnchorElement是HTMLElement的子类型 - 通过
类型断言
,linked的类型变得更加具体
,这样就可以访问a标签中的特有的属性和方法
了
<>第二种方式
这种方式不是很常用,大家作为了解即可,在React中 这种语法格式和jsx冲突 所以用不了的
// 如果我们指定的HTMLElement 是无法获取的href等属性的
const linked = <HTMLAnchorElement>document.getElementById('link')
console.log('a标签的值为:',linked)
console.dir($0)
可以打印当前第一个dom元素,并且在列表的最后可以看到该元素的类型
3.12.字面量类型
思考下面代码,两个变量类型分别是什么
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字面量(比如对象,数字等)都可以作为类型使用
使用场景:字面量类型配合联合类型一起使用(用来表示一组明确的可选值列表)
比如在贪吃蛇蛇的游戏中,游戏的方向只能是 上、下、左、右,的其中一个。
const changeDirection = (direction: 'up' | 'down' | 'left' | 'right') => {
console.log('输入的方向是:', direction);
}
changeDirection('up')
changeDirection('1')
- 参数direction的值只能是 up/down/left/right的中的任意一个
- 相对于string 类型,使用字面量类型更加精确严谨。
3.13.枚举
枚举的功能类似于字面量类型+联合类型的组合功能,也可以表示一组明确的可选值。
**枚举:定义一组命名常量。**它描述一个值,该值可以是这些命名常量中的一个。
enum Direction { Up, Down, Left, Right }
const changeDirection = (direction: Direction) => {
console.log('输入的方向是:', direction);
}
changeDirection(Direction.Up)
- 使用
enum关键字
来定义枚举。 - 约定枚举名称、枚举中的
值以大写字母开头
。 - 枚举中的多个值通过
,(逗号)
分隔。 - 定义好枚举后,
直接
使用枚举名称
作为类型注解
注意:形参中的direction的枚举类型为Direction,那么实参的值就应该是枚举Direction成员中的任意一个
访问枚举成员:dirction.Up
,类似于JS中的对象,直接通过 点(.)语法访问枚举的成员。
枚举成员的值以及数字枚举
当我们把枚举成员作为了函数的实参,那么它的值是什么呢?
当我们把将鼠标放到对应的枚举类型上时,可以看到枚举成员Up的值是为0的。
注意:其实枚举成员是有值的,默认为:从0开始的自增的数值。
我们把枚举成员的值为数字的枚举,称为数字枚举
当前也可以给枚举成员中得到成员初始化值
enum Direction { Up = 10, Down, Left, Right }
字符串枚举
字符串枚举:枚举的成员的值是字符串
enum DirectionStr { Up = 'UP', Down = 'DOWN', Left = 'LEFT', Right = 'RIGHT' }
console.log(DirectionStr.Left);
注意:字符串枚举没有自增长行为,因此,字符串枚举的每个成员都必须有初始值。
3.14.any类型
原则上:不推荐使用any 这个会让TypeScript变为 AnyScript (失去TS类型保护的优势 )
因当值为any类型时,可以对该值进行任意操作,并且不会有代码提示
let obj: any = { x: 0 }
obj.bar = 100
obj()
const n: number = obj
上面的代码操作不会有任何类型的错误提示,即使可能存在报错!
应该尽可能避免使用any,除非临时使用any来避免 书写很长 很复杂的类型
其他隐式具有any类型的情况:
- 声明变量不提供类型也不提供默认值
- 函数参数不加类型
注意,这两种情况下都应该去提供类型,防止出现运行时错误
3.15.typeof
在JavaScript中,提供了typeof操作符,用来获取数据的类型
实际上,TS也提供了 typeof操作符:可以在类型上下文中引用变量 或 属性的类型。
使用场景:根据已有变量的值,获取该值的类型来简化书写
// 使用typeof来简化类型的书写
let p = { x: 1, y: 2 }
const printPointInfo = (point: { x: number, y: number }) => {
console.log('x:', point.x);
console.log('y:', point.y);
}
printPointInfo(p)
console.log('----------------------------------------');
// 因为 ts 可以根据值 去推断属性的类型 那么可以简化书写
const printPointInfoNew = (point: typeof p) => {
console.log('new-x:', point.x);
console.log('new-y:', point.y);
}
printPointInfoNew(p)
- 使用
typeof
操作符获取变量p的类型,结果 和第一种相同 typeof
出现在类型注解的位置
(参数名称的冒号后面)所处的环境就在类型的上下文typeof
只能用来查询变量或者属性的类型
,无法查询其他形式的类型
(比如函数的调用类型)
4.TS中的高级类型
TS中的高级类型有很多,重点学习以下高级类型
- class类
- 类型兼容性
- 交叉类型
- 泛型 和 keyof
- 索引签名类型 和 索引查询类型
- 映射类型
4.1.class类
TypeScript全面支持ES2015中引入的 class关键字,并且为其添加了类型注解 和 其他语法 (如可见性修饰符等)
class的基本使用
class Person {
/**
* 姓名
*/
name: string
}
const person = new Person()
console.log(person);
- 根据TS中的类型推断,可以直到Person类的实例对象 person的类型是 Person
- TS中的class,不仅提供class的语法功能,也可以作为一种类型的存在
实例属性的初始化
/**
* 人
*/
class Person {
/**
* 姓名
*/
name: string;
/**
* 性别
*/
gender = '男生'
}
- 声明成员 age,类型为number(没有初始值)
- 声明成员 gender,并
设置初始值
,此时可以省略类型注解
(TS类型推论为string类型)
4.2.class的构造函数
class Person {
name: string
gender: string
constructor(name: string, gender: string) {
this.name = name
this.gender = gender
}
}
const person = new Person('张三','女生')
console.log(person.name)
console.log(person.gender)
console.log(person);
- 成员初始化(比如age:number)后,才可以通过 this.age访问实例成员。
- 需要为构造函数指定类型注解,否则会被隐式推断为any,构造函数不需要返回值类型
4.3.class类的实例方法
class Point{
// 这里如果给定默认值 那么就不用写构造方法了
x : number
y : number
constructor(x:number,y:number){
this.x = x
this.y = y
}
/**
* 计算方法
* @param num 计算的比例数
*/
scale(num: number):void{
this.x *= num
this.y *= num
console.log(this.x);
console.log(this.y);
}
}
const p = new Point(1,2)
p.scale(100)
- 方法的类型注解(参数和返回值)与函数的用法相同
4.4.class继承
类的继承有两种方式
- 通过 extends(继承父类)
- 通过implements(实现接口)
JS中只有 extends,但是TS提供了implements
extends
/**
* 动物通用父类
*/
class Animal {
move() {
console.log('Moving along!')
}
}
/**
* 小狗类
*/
class Dog extends Animal{
bark() {
console.log('汪汪!')
}
}
const dog = new Dog()
// 调用父类的移动方法
dog.move()
dog.bark()
- 通过extends关键字实现继承
- 子类Dog继承父类Animal,则Dog的实例对象dog就同时具有了父类的Animal 和 子类 Dog的所有的属性和方法
implements
interface CommonPrint {
/**
* 通用输出方法
* @param name 输出的信息
*/
print(name: string): void
}
/**
* 手机实现类-输出手机信息
*/
class PhonePrint implements CommonPrint {
print(name: string) {
console.log("当前手机的型号为:", name)
}
}
const phone = new PhonePrint()
phone.print('Iphone 16 Pro Max')
- 通过
implements
关键字让class
实现接口 PhonePrint
类是实现CommonPrint
意味着,PhonePrint
类中必须提供CommonPrint
接口中指定的所有方法和属性
4.5.类的可见性修饰符
类成员可见性:可以使用TS来控制class的方法 或 属性 对于calss外的代码是否可见
可见性修饰符包括:
- public(公有的)
- public:表示公有的,公开的,公有成员可以被任何地方访问(默认可见性)一般不写就是公有的
- protected(受保护的)
- private(私有的)、
公有的(public)
class PhonePrint implements CommonPrint {
public print(name: string) {
console.log("当前手机的型号为:", name)
}
}
受保护的(protected)
protected:表示受保护的,仅对其声明所在类和子类中(非实例对象)可见,对实例对象不可见!不可见!不可见!
class Father{
protected money:10000000000
}
class Son extends Father{
print(){
console.log('Father Money:',this.money)
}
}
const son = new Son()
son.print()
// 子类实例对象时不可见的 但是在对应的子类中 是可以访问得到
son.moeny
私有的(private)
私有属性或方法只能在当前类中可见,对其子类和实例对象也是不可见的!
class Father{
private money:10000000000
}
class Son extends Father{
print(){
console.log('Father Money:',this.money)
}
}
4.6.readonly只读属性
除了可见性修饰符之外,还有一个比较常见的修饰符号就是 readonly(只读修饰符)
readonly:表示只读,用来防止在构造函数之外对属性进行赋值
class Person {
readonly age: number
constructor(age: number) {
this.age = age
}
}
const person = new Person(12)
person.age = 11
// 接口
interface IPerson{
readonly name : string
}
let obj:IPerson = {
name : 'Jack'
}
obj.name = 'rose'
// {} 表示类型
let obj: { readonly name: string } = {
name: 'Jack'
}
obj.name = 'rose'
- 使用
readonly
关键字修饰的属性是只读
的,注意:readonly只能修饰属性,不能修饰方法 接口
或者{}
表示的类型,也可以使用readonly
4.7.类型兼容性
两种类型系统:
- Structural Type System (结构化类型系统)
- Nominal Type System(标明类型系统)
TS采用的是 结构化类型系统,也叫做 duck typing(鸭子类型),类型检查关注的是值所具有的形状。也就说,在结构类型系统中,如果两个对象具有相同的形状,则认为他们属于同一类型。
class NewPoint {
x: number
y: number
}
class NewPoint2d {
x: number
y: number
}
const p1: NewPoint = new NewPoint2d()
// 为变量赋值
p1.x = 12
p1.y = 33
// 打印当前实例
console.log(p1);
NewPoint
和NewPoint2d
是两个不同名称的类- 变量
p1
的类型
被显式标注为NewPoint
类型,但是他的值
确实Point2D
的实例,并没有类型的错误 - 因为
TS是结构化类型系统
,只检查NewPoint
和NewPoint2d
的结构
是否相同,(相同都具有 x 和 y 两个属性,属性类型相同) - 但是 在
Nominal Type System
中(比如C#
,Java
中),他们就是不同的类
,无法兼容
4.8.对象之间的类型兼容性
在结构系统中,如果两个对象具有相同的形状,则认为他们属于同一种类型,这种说法不准确
更准确的说:对于对象类型来说,y的成员至少与x相同,则x兼容y (成员多的可以赋值给少的)
class NewPoint {
x: number
y: number
constructor(x:number,y:number){
this.x = x
this.y = y
}
}
class NewPoint3d {
x: number
y: number
z: number
constructor(x:number,y:number,z:number){
this.x = x
this.y = y
this.z = z
}
}
const p1: NewPoint = new NewPoint3d(1,3,3)
// 打印当前实例
console.log(p1)
NewPoint3d
的成员至少 和NewPoint
相同,则NewPoint
兼容NewPoint3d
- 成员多的
Point3D
可以赋值给成员少的NewPoint
4.9.接口之间的兼容性
除了class之外,TS的其他类型也存在相互兼容的情况,包括
- 接口兼容性
- 函数兼容性
接口之间的兼容性,类似于class 并且class和interface之间也可以兼容
interface NewPoint {
x: number
}
interface NewPoint2d {
x: number
y: number
}
interface NewPoint3d {
x: number
y: number
z: number
}
// 类 和 接口之间也是相互兼容的
class NewPoint4d {
x: number
y: number
z: number
constructor(x:number,y:number,z:number){
this.x = x
this.y = y
this.z = z
}
}
let p99 : NewPoint3d
let p98 : NewPoint2d
// 声明p97 类型为 NewPoint
let p97 : NewPoint
p97 = new NewPoint4d(1,2,3)
console.log(p97);
4.10.函数之间的兼容性
函数之间的兼容性比较复杂,需要考虑
- 参数的个数
- 参数的类型
- 返回值类型
参数个数
参数个数,参数多的个数,可以兼容参数少的个数(参数少的可以赋值给参数多的)
// 函数参数的返回类型
let function1 = (a: number): void => { }
let function2 = (a: number, b: number): void => { }
// 参数少的function1 可以赋值给参数多的function2
function2 = function1
// 参数多的不能赋值给参数少的
function1 = function2
// 函数参数的返回类型
type F1 = (a: number) => void
type F2 = (a: number, b: number) => void
// 这里如果不给声明变量赋值初始值会报错
let f1: F1 = (a: number) => { }
let f2: F2 = (a: number, b: number) => { }
f2 = f1
console.log(f2);
let arr = ['a', 'b', 'c']
// 上面数组中,示例的类型为:(method) Array<string>.forEach(callbackfn: (value: string, index: number, array: string[]) => void, thisArg ?: any): void
arr.forEach(() => { })
arr.forEach((item) => {
console.log(item);
})
arr.forEach((item, index) => {
console.log(item, '-', index);
})
arr.forEach((item, index, arr) => {
console.log(item, '-', index, '-', arr);
})
- 参数少的可以赋值给参数多的,所以
function1
和 赋值给function2
- 数组
forEach
方法的第一个参数是回调函数,类型为 (value: string, index: number, array: string[]) => void - 再JS中省略用不到的函数参数实际上是非常常见的,这种使用方式促成了TS中函数类型之间的兼容性
- 并且因为回调函数是有类型的,所以TS会自动推导出 item index array的类型
-**
函数参数
函数参数,相同位置的参数类型要相同(原始类型)或兼容(对象类型)
type F1 = (a: number) => string
type F2 = (a: number, b: number) => string
let f1: F1 = (a: number) => ''
// 参数少的兼容参数多的,但是参数多的不能兼容参数少的
let f2: F2 = f1
console.log('f2的类型是:', f2);
函数类型F2 兼容 函数类型F1 ,因为F2 和 F1的第一个参数相同
返回值
返回值比较简单,我们只关心返回值的类型
即可
返回值是原始类型
// 下面这个是错误的一个演示
type F1 = (a: number) => number
type F2 = (a: number, b: number) => string
let f1: F1 = (a: number) => 0
let f2: F2 = f1
// 正确写法
type F1 = (a: number) => string
type F2 = (a: number, b: number) => string
let f1: F1 = (a: number) => ''
let f2: F2 = f1
返回值是对象类型
type F1 = () => { name: string }
type F2 = () => { name: string, age: number }
let f2: F2 = () => { return { name: '张三', age: 123 } }
let f1: F1
f1 = f2
console.log('f1',f1);
注意:
- 如果返回值类型是
原始类型
,此时两个类型要相同 比如 f1 和 f2 返回值类型都是string类型 - 如果返回值类型是
对象类型
,此时成员多
的可以赋值
给成员少
的 比如 下面的 f2 和 f1 ,f2的返回值类型 比 f1的返回值类型 多了个age
4.3.交叉类型
**交叉类型(&)**功能类似于接口继承(extends),用来组合多个类型为一个类型(常用于对象)
比如
interface Person {
name: string
}
interface Concat {
phone: string
}
type PersonAndConcat = Person & Concat
let obj: PersonAndConcat = {
name: '迪迦',
phone: '15256412345'
}
console.log("obj:", obj);
使用交叉类型后,新的类型PersonAndConcat
就同时 具备了 Person
和 Concat
的所有属性类型
相当于
type PersonAndConcat = { name: string, phone: string }
4.4.交叉类型 和 接口继承 之间的比对
交叉类型(&)和 继承(extends)的对比:
- 相同点:都可以实现对象类型的组合
- 不同点:两种方式都可以实现类型组合,对于同名属性之间,处理类型冲突的方式不同
// 继承
interface A {
print(value: number): string
}
interface B extends A {
print(value: string): string
}
// 交叉
interface C {
print(value: number): string
}
interface D {
print(value: string): string
}
type E = A & B
// 其实 A & B 可以理解为 print(value : string | number): string
上面代码接口继承的时候会报错(类型不兼容),但是交叉类型没有报错
4.5.*泛型
泛型是可以再保证安全的前提下,让函数等多种类型一起工作,从而实现复用,常用于:函数、接口、class中。
需求:创建一个id函数,传入什么类型数据,就返回该数据本身(参数和返回值类型一致)
// 上面函数 只能接受字符串类型的,如果其他的类型的就不兼容了,
// 可以使用any来处理,但是使用any后,就失去了TS类型保护
const getUserName = (username: string): string => { return username }
const getUserName = (username: any): any => { return username }
这个时候就可以使用泛型来处理,泛型在保证类型安全(不丢失类型信息)的同时,可以让函数等与多种不同类型一起工作,灵活复用。
创建泛型函数
// 第一种写法
const getUserName = <T>(username: T): T => { return username }
// 第二种写法
function getUserInfo<T>(value: T): T {
return value
}
type UserInfo = {
username: string
}
console.log(getUserName<UserInfo>({ username: '张三' }));
- 语法:在函数名称的后面添加<>(尖括号),尖括号中添加类型变量 例如此处的 T
- 类型变量 T ,是一种特殊类型的变量,他处理类型,而不是值
- 该类型变量相当于一个类型容器,能够捕获到用户提供的类型(具体何种类型,由用户调用该函数时指定)
- 因为T是类型,因此可以将其作为函数的参数和返回值类型,表示参数 和返回值具有相同的类型
- 类型变量Type,可以是任意合法的变量名称
调用泛型函数
getUserName<UserInfo>({ username: '张三' })
语法:
- 语法:在函数名称后面添加 <>(尖括号),尖括号中指定具体的类型,比如 此处的UserInfo
- 当传入UserInfo类型后,这个类型就会被函数声明时指定的类型变量 Type捕获到
- 次数Type的类型 就是 UserInfo,所以id参数和返回值类型也是UserInfo
这样通过泛型就做到了让 getUserName函数 和 多种不同的类型在一起工作,从而实现了复用的同时保证了类型的安全
简化函数的调用
const getUserName = <T>(username: T): T => { return username }
const getId = <T>(id: T): T => { return id }
let id = getId<string>('迪迦')
// 在调用函数的时候 可以省略 <类型>来简化泛型函数的调用
let id2 = getId('泰罗')
- 在调用泛型函数时,可以省略<类型>来简化泛型函数的调用
- 此时的TS内部会采用一种叫做
类型参数推断
的机制,来根据传入的实参自动推断出类型变量Type的类型
泛型约束
默认情况下,泛型函数的类型变量Type可以代表多个类型,这就导致无法访问任何属性。比如 getId(‘a’)调用函数时获取参数的长度
const getId = <T>(value: T): T => {
console.log(value.length);
return value;
}
function getIdNew<T>(value: T): T {
console.log(value.length);
return value;
}
T
可以代表任意类型
,无法保证一定存在length属性,比如number
类型就没有length
,此时需要为泛型 添加约束来收缩类型(缩窄类型取值范围)
添加泛型约束收缩类型,主要有以下两种方式:
- 指定更加具体的类型
- 添加约束
指定更加具体的类型
比如将类型 修改为T[] (T类型的数组),因为只要时数组就一定存在length属性了,因此就可以访问了
const getId = <T>(value: T[]): T[] => {
console.log(value.length);
return value;
}
function getIdNew<T>(value: T[]): T[] {
console.log(value.length);
return value;
}
console.log(getId(['张三','1','2']));
console.log(getId(['李四','3','4','5']));
添加约束
interface ILength {
length: number
}
const getId = <T extends ILength>(value: T): T => {
console.log(value.length);
return value;
}
function getIdNew<T extends ILength>(value: T): T {
console.log(value.length);
return value;
}
console.log(getId(['迪加', '1', '2']));
console.log(getId(['赛文', '3', '4', '5']));
- 创建描述约束的接口 ILength,该接口中要求提供length属性
- 通过
extends
关键字使用该接口,为泛型(类型变量)添加约束
- 该约束表示:传入的类型必须具有length属性
*多个泛型变量情况
泛型的类型变量可以有多个,并且类型变量之间还可以约束(比如,第二个类型变量受第一个类型变量约束)
const getPorp = <T, K extends keyof T>(obj: T, key: K) => {
console.log('Object:', obj);
console.log('Key:', key);
return obj[key]
}
console.log(getPorp({
name: "迪加奥特曼",
age: 999999999,
addrees: 'null'
}, 'name'));
- 添加了第二个类型变量 K,两个类型变量之间使用
(,)分隔
keyof
关键字 接受一个对象类型,生成其键名称(可能是字符串或者数字)的联合类型
- 上面案例中
keyof
T 实际上获取的 是 对象的所有键的联合类型,也就是'name'
|'age'
|'address'
- 类型变量
K 受 T
约束,可以理解为:K 只能是 T 所有键中的任意一个
,或者只能访问对象中存在的属性
泛型接口
泛型接口:接口也可以配合泛型来使用,增加其灵活性,增强其复用性
interface Book<T> {
getBookName: (value: T) => T
}
let book: Book<string> = {
getBookName(value) {
return value;
},
}
console.log(book.getBookName('Java开发入门!'));
- 在接口名称后面添加<类型变量>,那么这个接口就变成了泛型接口。
- 接口的类型变量,对接口中所有其他成员可见,也就是,接口中所有成员都可以使用类型变量
- 使用泛型接口时,需要显式指定具体的类型,(比如 Book )
- 此时,id方法的参数 和返回值类型都 string
数组是泛型接口
// 泛型数组
// 实际上JS中的数组在TS中就是一个泛型接口
const strs = ['a', 'b', 'c']
const nums = [1, 2, 3, 4, 5]
当我们在使用数组时,TS会根据数组的不同类型,来自动将类型变量设置为相应的类型。
泛型类
class GenericNumber<NumType> {
defaultValue: NumType
add: (x: NumType, y: NumType) => string = (x, y) => {
console.log(x);
console.log(y);
return 'success';
}
// 可以省略<>尖括号 当类中提供了constructor 并且提供了属性 那么就不需要在 显式的声明类型了
constructor(value: NumType) {
this.defaultValue = value
}
}
const myNum = new GenericNumber(100)
// 推荐明确指定泛型类型
const myNum1 = new GenericNumber<number>(100)
myNum1.defaultValue = 10
myNum1.add(1, 2)
console.log(myNum1);
-
类似于泛型接口,在class名称后面添加**<类型变量>**,这个类就成了泛型类
-
此处的add方法,采用的是箭头函数形式的书写方式
-
const myNum1 = new GenericNumber<number>(100)
类似于泛型接口,在创建class实例时,在类名后面通过**<类型>**来指定明确类型
泛型工具类
TS中内置一些常用的工具类,来简化一些TS中常见的操作
它们都是基于泛型实现的(泛型适用于类型,更加通用),并且时内置的,可以直接在代码中进行使用。
- Patial
- Readonly
- Pick<T,K>
- Record<K,T>
Patial
用来 创建一个类型,将 泛型T 的所有属性变成可选的。
// 泛型工具类
interface Props {
id: string,
age: number,
children: number[]
}
// 用来 创建一个类型,将 泛型T 的所有属性变成可选的。
type PartialProps = Partial<Props>
- 构造出来的 PartialProps 和 Props 结构相同,但是所有属性都变为可选了
Readonly
用来构造一个类型,将 泛型T的所有属性设置为 readonly(只读)
interface Props {
id: string,
age: number,
children: number[]
}
type ReadonlyProps = Readonly<Props>
let props: ReadonlyProps = { id: '1', age: 12, children: [1, 2, 3] }
props.children = 14
Pick
Pick<T,K> 从T中选择一组属性来构造新的类型
interface Props {
id: string,
age: number,
children: number[]
}
type PickProps = Pick<Props, 'id' | 'age'>
let pickProps: PickProps = { id: '1212', age: 12 }
console.log(pickProps);
- Pick工具类中两个类型 :1,表示选择谁的属性 2,表示选择哪几个属性
- 第二个参数,如果只选择一个属性,那么写一个参数就行了,多个 就需要使用 | 进行拼接
- 第二个参数,传入的参数只能是第一个参数中存在的属性
Record
用来构造一个对象类型,属性键 为K 属性类型为 T
type RecordObj = Record<'a' | 'b' | 'c', string[]>
let obj: RecordObj = {
a: ['1'],
b: ['2'],
c: ['3']
}
console.log(obj);
- Record工具类型有两个类型变量:
- 表示对象有哪些属性
- 表示对象属性的类型
- 构建的新对象类型RecordObj表示:这个对象的三个属性分别为 a,b,c 属性值的类型都是 string[]
4.6.索引签名类型
在大多数情况下,我们都可以是在使用对象前确定对象的结构,并为对象添加准确的类型。
当无法确定对象中有哪些属性
时,此时就用到索引签名类型
了
interface AnyObject {
[K: string]: number
}
let obj: AnyObject = {
a: 1,
b: 2
}
// 可以指定对应其他类型
interface AnyObject {
[K: number]: number
}
let obj: AnyObject = {
1: 1,
2: 2
}
- 使用
[K: string]
来约束接口中允许出现的属性名称。表示只要是string类型的属性名称,都可以出现在对象中。 - 这样对象中就可以 出现任意多个属性(比如 a,b)
K
只是一个占位符
,可以替换成任意合法的变量名称
- Js中对象({})的建都是string类型
JS数组中的 也用到索引签名
4.7.映射类型
映射类型:基于旧类型创建新的类型,减少重复提升开发效率
// 这里相当于定义了键
type PrposKeys = 'x' | 'y' | 'z'
type TypeDefault = { x: number, y: number, z: number }
type Type = { [Key in PrposKeys]: number }
let obj: Type = {
x: 1232,
y: 213,
z: 12132
}
console.log(obj);
- 映射类型是基于索引签名类型的,所以该语法类似于索引签名类型,也使用了
[]
- Key
in
PropKeys 表示Key 可以是PrposKeys
类型中的任意一个
,类似于forin (let k in obj)
- 使用映射类型创建的对象类型
Type
和TypeDefault
完全相同 - 注意:映射类型只能在类型别名中使用,不能在接口中使用
4.8.映射类型(keyof)
映射类型处理根据联合类型创建新类型,还可以根据对象类型来创建
type PrposKeys = { x: number, y: number, z: number, note: string }
type PropsKeys2 = { [Key in keyof PrposKeys]: number | string }
let obj: PropsKeys2 = {
x: 1232,
y: 213,
z: 12132,
note: '测试数据'
}
console.log(obj);
-
首先,先执行
keyof
Props获取到对象类型的Props中所有建的联合类型,x: number, y: number, z: number, note: string -
然后 Key
in
就表示 Key 可以是Props中所有的键名称中的任意一个
4.9.索引查询类型
查询单个
刚刚使用的 T[P]
语法,在TS中叫做索引查询类型
,作用就是:用来查询属性的类型
type Props = { name: string, age: number }
type TypeA = Props['age']
- 注意:[]中的属性必循存在于被查询的类型中,否则就会报错
查询多个
type Props = { name: string, age: number }
// type TypeA = Props['age' | 'name']
type TypeA = Props[keyof Props]
- 使用
keyof
操作符获取Props
中所有键对应的类型,结果为:string | number
5.**类型声明文件
在项目开发中,几乎所有的JS应用都会引入许多第三方库来完成任务需求,这些第三方库不管是不是TS编写的,最终都会变成JS代码,才能给开发者使用,我们直到TS提供了类型,才有了代码提示和类型保护等机制。
在项目开发使用第三方库的时候,你会发现他们几乎都有相应的TS类型,这些类型怎么来的呢? 类型声明文件
类型声明文件:用来为已存在的JS库提供类型信息
这样在TS项目中使用这些库时,就会像TS一样,有代码提示、类型保护等机制了。
- TS的两种文件类型
- 类型声明文件的使用说明
TS中的两种文件类型
- .ts文件
- 既可以包含类型信息,又可以包含执行代码
- 可以被编译成.js文件,然后执行代码
- 用途:编写程序代码的地方
- .d.ts文件
- 只包含类型信息的类型声明文件
- 不会生成.js文件,仅用于提供类型信息
- 用途:为js提供类型信息
总结:.ts(代码实现文件) .d.ts(类型声明文件)
类型声明文件的使用说明
在使用TS开发项目时,类型声明文件的使用包括以下两种方式:
- 使用已有的类型声明文件
- 创建自己的类型声明文件
先学会怎么用别人得到,再去写自己的
内置类型声明文件
TS为JS运行时可用的所有标准化内置了API都提供了声明文件,比如我们在使用数组的时候,数组的所有方法都会有相应的代码提示以及类型信息
实际上这些都是TS提供的内置类型声明文件,可以通过Ctrl + 鼠标左键
来查看内置类型声明文件
第三方库的类型声明文件
目前,几乎所有常用的第三方库都有相应的类型声明文件
第三方库的类型声明文件主要有两种形式:
- 库自带类型的声明文件
- 有DefinitelyTyped提供
库自带的类型声明文件,例如axios
这种情况下,正常导入该库,TS就会自动加载库自己的类型声明文件,用来提供该库的类型声明。
由DefinitelyTyped提供
DefinitelyTyped是一个github仓库,用来提供高质量的TypeScript类型声明
可以通过npm来下载该仓库提供的TS类型声明包,这些包的名称格式为: @types/*
比如下载 @types/react
可以尝试使用
npm i --save-dev @types/react(库的一些名称)
安装后,TS会自动加载该类声明包,以提供该库的类型声明。
5.1.创建自己的类型声明文件
项目内共享类型
项目内共享类型:如果多个 .ts
文件中都用到同一个类型,此时可以创建 .d.ts
文件提供该类型,实现类型共享。
操作步骤:
-
创建
user.d.ts
类型声明文件-
// user.d.ts export type UserInfo = { username: string, password: string }
-
-
创建需要共享的类型,并使用
export导出
(TS中中的类型也可以使用 import/export 实现模块化功能) -
在需要使用共享类型的 .ts 文件中,通过import 导入即可(.d.ts后缀导入时,直接忽略即可)
-
import { UserInfo } from "../types/user"; let user: UserInfo = { username:'137230256', password:'123456@111' } console.log('user:[',user,']');
-
为已有的JS文件提供类型声明
什么时候需要为已有JS文件提供类型声明呢?
- 在将JS项目迁移到 TS项目的时候,为了让已有的 .js 文件有类型声明
- 成为库作者,创建库给其他人使用
注意:类型声明文件的编写 与 模块化的方式相关,不同的模块化方式有不同的写法。
演示:基于最新的 ESModule(import/export)
来为已有的.js
文件,创建类型声明文件
开发环境准备:使用webpack
搭建,通过 ts-loader处理 .ts 文件
说明:TS文件中也可也使用 .js 文件,到导入 .js 文件时,TS会自动加载 和 .js同名的 .d.ts文件,以提供类型声明
declare关键字
用于类型声明,为其他地方(比如,.js 文件)已经存在的变量 声明类型,而不时创建一个新的变量
- 对于
type
、interface
等这些明确
就是TS类型
的(只能在TS中使用的),可以省略
declare
关键字 - 对于
let
funcation
等具有双重含义
的,(在JS 和 TS 中都可以使用),应该使用 declare关键字,明确指定此处用于类型声明
1.创建JS文件
user.js
export const getUserInfo = (params)=>{
console.log('获取用户信息成功!');
console.log('params:',params);
}
2.创建 .d.ts文件
user.d.ts
// 为方法的请求参数添加类型
declare type UserInfo = {
username: string,
password: string
}
// 为js文件 的方法添加类型
declare function getUserInfo(params:UserInfo):void
// 导出JS文件
export { getUserInfo }
// 或者
module.exports = { getUserInfo };
3.测试
import { getUserInfo, UserInfoParams } from "./user";
// 调用获取用户信息方法
let params: UserInfoParams = {
username: '1232',
password: '2132434'
}
console.log(params);
console.log('------------------------------------');
getUserInfo(params)
如果无法使用 export 导出 js方法 那么可以换一种思路
package.json
{
"compilerOptions": {
"module": "commonjs"
}
}
user.js
function getUserInfo(params) {
console.log('获取用户信息成功!');
console.log('params:', params);
}
module.exports = { getUserInfo };