TypeScript学习笔记

99 阅读29分钟

1.TypeScript与JavaScript区别

TypeScript属于静态类型的编程语言;JavaScript属于动态类型的编程语言

静态类型:编译期做类型检查;动态类型:执行期做类型检查

2.TypeScript相对JS的优势

  1. 更早(写代码的同时)发现错误,减少找Bug、改Bug时间,提升开发效率。

  2. 程序中任何位置的代码都有代码提示,随时随地的安全感,增强了开发体验。

  3. 强大的类型系统提升了代码的可维护性,使得重构代码更加容易

  4. 支持最新的ECMAScript语法,优先体验最新的语法,让你走在前端技术的最前沿。

  5. TS类型推断机制,不需要在代码中的每个地方都显示标注类型,让你在享受优势的同时,尽量降低了成本。

    除此之外,Rvue3源码使用TS重写、Angular默认支持TS、React与TS完美配合,TypeScript已成为大中型前端项目的首先编程语言。
    

3.安装编译TS工具包

Node.js/浏览器,只认识JS代码,不认识TS代码。需要将TS代码转换成JS代码,才能运行。

3.1基础运行TS的步骤

安装命令:npm i -g typescript

创建一个xxx.ts文件

将ts转js 命令行执行:tsc ./xxx.ts

3.2简化运行TS的步骤

每次修改后都要重复执行命令,才能运行TS代码,太繁琐

  1. 简化方式:使用ts-node包,直接在Node.js中执行TS代码
  2. 安装命令:npm i -g ts-node(ts-node包提供了ts-node命令)
  3. 使用方式:ts-node xxx.ts 注:ts-node命令在内部偷偷的将TS->JS,再运行JS代码。

4.TypeScript常用类型

将TS的常用基础类型细分为两类

  1. JS已有类型
  • 原始类型:number/string/boolean/null/undefind/symbol
  • 对象类型:object(包括,数组,对象,函数等对象)
  1. TS新增类型
  • 联合类型、自定义类型(类型别名)、接口、元组、字面量类型、枚举、void、any等。

4.1原始类型

let age:number=13

let myName:string='lily'

let isLoading:boolean=false

let a:null=null

let b:undefined=undefined

let s:symbol=Symbol()

4.2数组类型

// 加了小括号表示数组中可以有number或则string类型
let arr:(number|string)[]=[1,'a']
//不加小括号表示arr可以是一个数字或则一个数组(数组里面必须是字符串类型)
let arr1:number|string[]=['a','b']
let arr2:number|string[]=123

|(竖线)在TS中叫做联合类型(由两个或则多个其他类型组成的类型,表示可以是这些类型中的任一种)

4.2.1类型别名

类型别名(自定义类型):为任意类型取别名。 使用场景:当同一类型(复杂)被多次使用时,可以通过类型别名,简化该类型的使用

type CustomArray=(number|string)[]

let arr1:CustomArray=['a',1,'b']
let arr2:CustomArray=['c',1]

4.3函数类型

函数的类型实际上指的是:函数参数返回值的类型

函数指定类型两种方式:

  1. 单独指定参数、返回值得类型
  2. 同时指定参数、返回值得类型

4.3.1 单独指定参数、返回值得类型

function add(num1:number,num2:number):number{
    return num1+num2
}
const add=(num1:number,num2:number):number=>{
    return num1+num2
}
console.log(add(1,2))

4.3.2 同时指定参数、返回值得类型

const add:(num1:number,num2:number)=>number=(num1,num2)=>{
    return num1+num2
}

注:这种形式只适用于函数表达式

4.3.3 没有返回值

function greet(myName:string):void{
    console.log(myName)
}

4.3.4 可选参数

使用函数实现某个功能时,参数可以传也可以不穿,这种情况下可以用可选参数

可选参数:在可传可不传的参数名称后面添加?(问号)

注:可选参数只能出现在参数列表的最后,也就是说可选参数后面不能出现必选参数

function mySlice(start?:number,end?:number):void{
    console.log("起始索引:"+start,"结束索引:"+end)
}
mySlice()
mySlice(1)
mySlice(1,2)

4.4 对象类型

JS中的对象是由属性和方法构成的,而TS中对象的类型就是在描述对象的结构(有什么类型的属性和方法)

对象类型的写法:

//写法一:
let person:{name:string;sayhi():void;greet(name:string):void}={
    name:'zyt',
    sayhi(){},
    greet(name){}
}
//写法二:
let person2:{
    name:string
    sayhi():void
    greet(name:string):void
}={
    name:'zyt',
    sayhi(){},
    greet(name){}
}
  1. 直接使用{来描述对象结构。属性采用属性名:类型的形式;方法采用方法名():返回值类型的形式。
  2. 如果方法有参数,就在方法名后面的小括号中指定参数类型(比如: greet(name: string): void)。
  3. 在一行代码中指定对象的多个属性类型时,使用;(分号)来分隔。
  • 如果一行代码只指定一个属性类型(通过换行来分隔多个属性类型),可以去掉;(分号)。
  • 方法的类型也可以使用箭头函数形式(比如:{sayHi: () => void }) 。

4.4.1 可选属性

对象的属性或方法,也可以是可选的,此时就用到可选属性了。 比如,我们在使用axios({ ...})时,如果发送GET请求,method属性就可以省略。

function  myAxios(config:{url:string;method?:string}){}

myAxios({
    url:''
})

可选属性使用?来表示

4.4.2 接口

接口的使用:

当一个对象类型被多次使用时,一般会使用接口( interface)来描述对象的类型,达到复用的目的。

说明:

  1. 使用interface关键字来声明接口。
  2. 接口名称(比如,此处的IPerson),可以是任意合法的变量名称。
  3. 声明接口后,直接使用接口名称作为变量的类型
  4. 因为每一行只有一个属性类型,因此,属性类型后没有;(分号)。
// 接口
interface IPerson{
    name:string
    age:number
    sayHi():void
}

let person:IPerson={
    name:'zyt',
    age:18,
    sayHi(){}
}

interface(接口)和type(类型别名)的对比

  • 相同点:都可以给对象指定类型
  • 不同点:
    • 接口:只能为对象指定类型
    • 类型别名,不仅可以为对象指定类型,实际上可以为任意类型指定别名
// 接口
interface IPerson{
    name:string
    age:number
    sayHi():void
}

//类型别名
type IPerson={
    name:string
    age:number
    sayHi():void
}
//使用
let person:IPerson={
    name:'zyt',
    age:18,
    sayHi(){}
}

接口复用:

如果两个接口之间有相同的属性或方式,可以将公共的属性或方法抽离出来,通过继承来实现复习

使用extends(继承)关键字实现了接口child继承Parent

interface Parent{x:number;y:number}

interface child extends Parent{z:number }

let a:child={
    x:1,
    y:2,
    z:3
}

4.5 元组

元组类型是另一种类型的数组。它确切得知道包含多少个元素,以及特定索引对应的类型

let position:[number,number]=[1,2]
  1. 元组类型可以确切得标记出有多少个元素,以及每个元素的类型。
  2. 示例中,元素有两个元素,每个元素的类型都是number

ts的类型推论机制会帮助提供类型 2种常见场景 声明变量并初始化 决定函数返回值 推荐:能省略注解的地方就省略(并不是偷懒,而是提升开发效率)

类型断言

使用as关键字来实现类型断言

const aLink=document.getElementById('link') as HTMLAnchorElement
  1. 关键字as后面的类型是一个更加具体的类型(HTMLAnchorElement是HTMLElement的子类型)。
  2. 通过类型断言,aLink的类型变得更加具体,这样就可以访问a标签特有的属性或方法了。 另一种写法
const Alink=<HTMLAnchorElement>document.getElementById('link')
技巧:在浏览器控制台,通过console.dir()打印DOM元素,在属性列表最后看该元素的类型

字面量类型

let str1='Hello TS'  //类型为string
const str2='Hello TS'//类型为Hello TS

str1是一个变量(let),它的值可以是任意字符串,所以类型为:string str2是一个常量(const),它的值不能变化只能是'Hello TS',所以它的类型为Hello TS 注意:此处的'Hello TS',就是一个字面量类型。也就是说某个特定的字符串也可以作为TS中的类型除字符串外,任意的JS字面量(比如,对象,数字等)都可以作为类型使用。

使用模式:字面量类型配合联合一起使用 使用场景:用来表示一组明确可选值列表 比如贪吃蛇游戏方向只能是上下左右任一个

function changeDirection(direction:'up'|'down'|'right'|'left'){
    console.log(direction)
}
changeDirection('down')

相对于string类型,使用字面量类型更加精确、严谨。

枚举

注意:形参direction的类型为枚举Direction,那么,实参的值就应该是枚举Direction成员的任意一个。访问枚举成员:类似于JS中的对象,直接通过点(.)语法访问枚举的成员。

enum Direction{
    up,
    down,
    right,
    left
}
function changeDirection(direction:Direction){
    console.log(direction)
}
changeDirection(Direction.down)

注意: 注意:枚举成员是有值的,默认为:从0开始自增的数值。 我们把,枚举成员的值为数字的枚举,称为:数字枚举。 当然,也可以给枚举中的成员初始化值。

//例一
enum Direction{
    up=10,
    down,   //11
    right,  //12
    left    //13
}
//例二
enum Direction{
    up=2,
    down=4,
    right=6,
    left=8
}

字符串枚举:枚举成员的值是字符串。 字符串枚举没有自增长行为,因此,字符串枚举的每个成员必须有初始值

enum Direction{
    up='UP',
    down='DOWN',
    right='LEFT',
    left='RIGHT'
}

编译后

var Direction;
(function (Direction) {
    Direction["up"] = "UP";
    Direction["down"] = "DOWN";
    Direction["right"] = "LEFT";
    Direction["left"] = "RIGHT";
})(Direction || (Direction = {}));

枚举是TS为数不多的非JavaScript类型级扩展(不仅仅是类型)的特性之一。 因为∶其他类型仅仅被当做类型,而枚举不仅用作类型,还提供值(枚举成员都是有值的)。 也就是说,其他的类型会在编译为JS代码时自动移除。但是,枚举类型会被编译为JS代码!

说明:枚举与前面讲到的字面量类型+联合类型组合的功能类似,都用来表示一组明确的可选值列表。 一般情况下,推荐使用字面量类型+联合类型组合的方式,因为相比枚举,这种万式更加直观、简洁、高效。

any类型

原则:不推荐使用any!这会让TypeScript变为“AnyScript”(失去TS类型保护的优势)。 因为当值的类型为any时,可以对该值进行任意操作,并且不会有代码提示。

typeof

js提供了typeof操作符,用来在js中获取数据类型。

console.log(typeof 'Hello world')//打印出string

ts也提供了typeof操作符:可以在类型上下文中引用变量或属性(类型查询) 使用场景:根据已有变量的值,获取该值得类型,来简化类型书写

let p={x:1,y:2}
function formatPoint(point:{x:number;y:number}){}
formatPoint(p)
let p={x:1,y:2}
function formatPoint(point:typeof p){}
formatPoint(p)
  1. 使用typeof操作符来获取变量p的类型,结果与第一种(对象字面量形式的类型)相同。
  2. typeof出现在类型注解的位置(参数名称的冒号后面)所处的环境就在类型上下文(区别于JS代码)。
  3. 注意: typeof只能用来查询变量或属性的类型,无法查询其他形式的类型(比如,函数调用的类型)。

5. TypeScript高级类型

5.1 class类

5.1.1 class的基本使用

TypeScript全面支持ES2015中引入的class关键字,并为其添加了类型注解和其他语法(比如,可见性修饰符等)。class 基本使用,如下:

class Person{}

const p=new Person()

说明:

  1. 根据TS中的类型推论,可以知道Person类的实例对象p的类型是Person。
  2. TS中的class,不仅提供了class的语法功能,也作为一种类型存在
class Person{
    age:number
    gender='男'
}

说明:

  1. 声明成员age,类型为number ( 没有初始值)。
  2. 声明成员gender,并设置初始值,此时,可省略类型注解(TS 类型推论为string类型)。

5.1.2 构造函数(实现实例属性初始化)

class Person{
    age:number
    gender:string
    constructor(age:number,gender:string){
        this.age=age
        this.gender=gender
    }
}
const p= new Person(18,'男')
console.log(p.age,p.gender)

说明:

  1. 成员初始化(比如,age: number)后,才可以通过this.age来访问实例成员。
  2. 需要为构造函数指定类型注解,否则会被隐式推断为any;构造函数不需要返回值类型

5.1.3 实例方法

class Point{
    x=1
    y=1
    scale(n:number):void{
        this.x*=n
        this.y*=n
    }
}
const p=new Point()
p.scale(10)
console.log(p.x,p.y)

方法的类型注解(参数和返回值)与函数用法相同。

5.1.4 class继承

类继承的两种方式: 1 extends (继承父类) 2 implements (实现接口), 说明: JS中只有extends,而implements是TS提供的。

(1)extends继承

//1. 通过extends关键字实现继承。
//2. 子类Dog继承父类Animal,则Dog的实例对象dog就同时具有了父类Animal和子类Dog的所有属性和方法。
class Animal{
    move(){console.log('走两步')}
}
class Dog extends Animal{
    name='二哈'
    back(){ console.log('汪汪汪')}
}
const d=new Dog()
d.move()
d.back()
console.log(d.name)

(2)implements接口

interface Singable{
    name:string
    sing():void
}
class Person implements Singable{
    name='jack'
    sing(){
        console.log('下一个天亮')
    }
}

说明:

  1. 通过implements关键字让class实现接口。
  2. Person类实现接口Singable意味着,Person 类中必须提供Singable接口中指定的所有方法和属性。

5.1.5 修饰符

  1. 可见性修饰符public 表示公有的、公开的、公有成员可以被任何地方访问,默认可见性
//父类
class Animal{
    public move(){console.log('走两步')}
}

const a=new Animal()
a.move()

//子类
class Dog extends Animal{
    back(){ console.log('汪汪汪')}
}
const d=new Dog()
d.move()

说明:

  • 在类属性或方法前面添加public关键字,来修饰该属性或方法是共有的。
  • 因为 public是默认可见性,所以,可以直接省略
  1. 可见性修饰符protected 表示受保护的,仅对其声明所在类和子类中(非实例对象)可见。
//父类
class Animal{
    protected move(){console.log('走两步')}
}

const a=new Animal()
//a.move()//实例不能访问

//子类
class Dog extends Animal{
    back(){ 
        console.log('汪汪汪')
        this.move()//可以访问
    }
}
const d=new Dog()
//d.move()//实例不能访问

说明:

  • 在类属性或方法前面添加protected关键字,来修饰该属性或方法是受保护的。
  • 在子类的方法内部可以通过this来访问父类中受保护的成员,但是,对实例不可见!
  1. 可见性修饰符private 表示私有的,只在当前类中可见,对实例对象以及子类也是不可见的。
//父类
class Animal{
    //私有的
    private _run_(){
        console.log('Animal 内部辅助函数')
    }
    //受保护的
    protected move(){
        this._run_()
        console.log('走两步')
    }
    run(){
        this._run_()
        this.move()
        console.log('跑起来')
    }
}

const a=new Animal()

//子类
class Dog extends Animal{
    back(){ 
        console.log('汪汪汪')
        this.move()//可以访问
    }
}
const d=new Dog()

说明:

  • 在类属性或方法前面添加private关键字,来修饰该属性或方法是私有的。
  • 私有的属性或方法只在当前类中可见,对子类和实例对象也都是不可见的!
  1. readonly只读修饰符 表示只读用来防止在构造函数之外对属性进行赋值
class Person{
    //注意:只要是readolny来修饰的属性,必须手动提供明确的类型
    readonly age:number=18
    constructor(age:number){
        this.age=age
    }
    setAge(){
        this.age=20//报错,不可修改
    }
}

使用readonly关键字修饰该属性是只读的,注意只能修饰属性不能修饰方法。 注意∶属性age后面的类型注解(比如,此处的number)如果不加,则age的类型为18(字面量类型)。 接口或者{}表示的对象类型,也可以使用readonly

5.2类型兼容器

两种类型系统: 1Structural Type System(结构化类型系统)2Nominal Type System(标明类型系统)。 TS采用的是结构化类型系统,也叫做ducktyping(鸭子类型),类型检查关注的是值所具有的形状。也就是说,在结构类型系统中,如果两个对象具有相同的形状,则认为它们属于同一类型。

class Point{
    x:number
    y:number
}
class Point2D{
    x:number
    y:number
}
const p:Point=new Point2D()
  1. Point和Point2D是两个名称不同的类。
  2. 变量p的类型被显示标注为Point类型,但是,它的值却是Point2D的实例,并且没有类型错误。
  3. 因为TS是结构化类型系统,只检查Point和Point2D的结构是否相同(相同,都具有x和y两个属性,属性类型也相同)
  4. 但是,如果在Nominal Type System 中(比如,C#、Java等),它们是不同的类,类型无法兼容。

5.2.1 对象之间的类型兼容性

在结构化类型系统中,如果两个对象具有相同的形状,则认为它们属于同一类型,这种说法并不准确。更准确的说法:对于对象类型来说, y的成员至少与x相同,则x兼容y(成员多的可以赋值给少的)。

class Point{
    x:number
    y:number
}
class Point3D{
    x:number
    y:number
    z:number
}
const p:Point=new Point3D()
  1. Point3D的成员至少与Point相同,则 Point兼容Point3D。
  2. 所以,成员多的Point3D可以赋值给成员少的Point。

5.2.2 接口之间的类型兼容性

除了class之外,TS中的其他类型也存在相互兼容的情况,包括:1接口兼容性2函数兼容性等。 接口之间的兼容性,类似于class。并且,class和interface之间也可以兼容。

interface Point{
    x:number
    y:number
}
interface Point2D{
    x:number
    y:number
}
interface Point3D{
    x:number
    y:number
    z:number
}
let p1:Point
let p2:Point2D={x:1,y:2}
let p3:Point3D={x:1,y:2,z:3}
//正确示范
p1=p2
p2=p1
p1=p3
//错误示范
// p3=p1

//class和interface之间的兼容
class Point4D{
    x:number
    y:number
    z:number
}
p2=new Point4D()

5.2.3 函数之间的类型兼容性

函数之间兼容性比较复杂,需要考虑: 1 参数个数2参数类型3返回值类型。

  1. 参数个数,参数多的兼容参数少的(或者说,参数少的可以赋值给多的)
type F1=(a:number)=>void
type F2=(a:number,b:number)=>void
let f1:F1=()=>{}
let f2:F2=f1
  • 参数少的可以赋值给参数多的,所以,f1 可以赋值给f2。
  • 数组forEach方法的第一个参 数是回调函数,该示例中类型为: (value: string, index: number, array: string) => void。
  • 在JS中省略用不到的函数参数实际上是很常见的,这样的使用方式,促成了TS中函数类型之间的兼容性
  • 并且因为回调函数是有类型的,所以,TS会自动推导出参数item、 index、array 的类型。
  1. 参数类型 相同位置的参数类型要相同或兼容
//原始类型
type F1=(a:number)=>void
type F2=(a:number)=>void
let f1:F1=()=>{}
let f2:F2
f2=f1
//对象类型
interface Point2D{
    x:number
    y:number
}
interface Point3D{
    x:number
    y:number
    z:number
}
type F2=(p:Point2D)=>void//相当于有两个参数
type F3=(p:Point3D)=>void//相当于有三个参数

let f2:F2=()=>{}
let f3:F3

f3=f2
  • 注意:此处与前面的接口兼容冲突
  • 技巧:把对象拆开,把每个属性看作一个个参数,则,参数少的f2可以赋值给参数多的f3
  1. 返回值类型 只关注返回类型本身即可
//原始类型:
type F5=()=>string
type F6=()=>string

let f5:F5
let f6:F6
f5=f6

//对象类型:
type F7=()=>{name:string}
type F8=()=>{name:string;age:number}

let f7:F7
let f8:F8

f7=f8
  • 如果返回值类型是原始类型,此时两个类型要相同,比如,左侧类型F5和F6。
  • 如果返回值类型是对象类型,此时成员多的可以赋值给成员少的,比如,右侧类型F7和F8。

5.3交叉类型

交叉类型(&):功能类似于接口继承(extends),用于组合多个类型为一个类型( 常用于对象类型)。

interface Person{
    name:string
    say():number
}
interface Contact{
    phone:string
}
type PersonDetail=Person&Contact

let obj:PersonDetail={
    name:'jsck',
    phone:'136xxxxxx',
    say() {
        return 1 
    },
}

说明:使用交叉类型后,新的类型PersonDetail就同时具备了Person和Contact的所有属性类型。 相当于

type PersonDetail={ 
    name:string
    say():number
    phone:string
}

5.3.1 交叉类型(&)和接口继承( extends)的对比

  • 相同点: 都可以实现对象类型的组合。
  • 不同点:两种方式实现类型组合时,对于同名属性之间,处理类型冲突的方式不同
interface A{
    fn:(value:number)=>string
}
interface B extends A{
    fn:(value:string)=>string
}

interface A{
    fn:(value:number)=>string
}
interface B {
    fn:(value:string)=>string
}
type C=A&B
let c:C={
    fn(value:number|string){
        return ''
    }
}
c.fn(1)

以上代码,接口继承会报错(类型不兼容);交叉类型没有错误,可以简单理解为:

fn:(value:string|number)=>string

5.4泛型和keyof

泛型是可以在保证类型安全前提下,让函数等与多种类型一起工作,从而实现复用,常用于:函数、接口、class中。 需求:创建一个id函数,传入什么数据就返回该数据本身(也就是说,参数和返回值类型相同)。

//使用泛型来创建一个函数
function id<Type>(value:Type):Type{
    return value
}
//调用泛型函数
const num=id<number>(10)
//以string类型调用泛型函数
const str=id<string>('a')
  1. 语法:在函数名称的后面添加<> (尖括号),尖括号中指定具体的类型,比如,此处的number。
  2. 当传入类型number后,这个类型就会被函数声明时指定的类型变量Type捕获到。
  3. 此时,Type的类型就是number,所以,函数id参数和返回值的类型也都是number。

同样,如果传入类型string,函数id参数和返回值的类型就都是string。 这样,通过泛型就做到了让id函数与多种不同的类型-起工作,实现了复用的同时保证了类型安全

5.4.1 简化调用泛型函数

  1. 在调用泛型函数时,可以省略<类型>来简化泛型函数的调用
  2. 此时,TS内部会采用一种叫做类型参数推断的机制,来根据传入的实参自动推断出类型变量Type的类型。
  3. 比如,传入实参10,TS会自动推断出变量num的类型number,并作为Type的类型。
const num=id(10)
  • 推荐:使用这种简化的方式调用泛型函数,使代码更短,更易于阅读。
  • 说明:当编译器无法推断类型或者推断的类型不准确时,就需要显式地传入类型参数。

5.4.2 泛型约束

泛型约束:默认情况下,泛型函数的类型变量Type可以代表多个类型,这导致无法访问任何属性。比如,id('a') 调用函数时获取参数的长度:

function id<Type>(value:Type):Type{
    console.log(value.length)//报错不能访问
    return value
}

说明: Type可以代表任意类型,无法保证一定存在length属性,比如number类型就没有length。此时,就需要为泛型添加约束来收缩类型(缩窄类型取值范围)。

5.4.3添加泛型约束收缩类型

添加泛型约束收缩类型,主要有以下两种方式: 1指定更加具体的类型2添加约束。

  1. 指定更加具体的类型
function id<Type>(value:Type[]):Type[]{
    console.log(value.length)
    return value
}

比如,将类型修改为Type[] (Type类型的数组),因为只要是数组就一定存 在length属性,因此就可以访问了。 2. 添加约束

interface ILength{
    length:number
}

function id<Type extends ILength>(value:Type):Type{
    console.log(value.length)
    return value
}
id(['a','b'])
id('abc')
id({length:10})
//错误示范
id(123)
  • 创建描述约束的接口ILength,该接口要求提供length属性。
  • 通过extends关键字使用该接口,为泛型(类型变量)添加约束。
  • 该约束表示:传入的类型必须具有length属性 注意:传入的实参(比如,数组)只要有length属性即可,这也符合前面讲到的接口的类型兼容性。

5.4.4 泛型类

class也可以配合泛型来使用

class GenericNumber<NumType>{
    defaultValue:NumType
    add:(x:NumType,y:NumType)=>NumType

    constructor(value:NumType){
        this.defaultValue=value
    }
}

// const myNum=new GenericNumber<number>()
// myNum.defaultValue=10

//简写方式
const myNum=new GenericNumber(100)

说明:

  1. 类似于泛型接口,在class名称后面添加<类型变量>,这个类就变成了泛型类。
  2. 此处的add方法,采用的是箭头函数形式的类型书写方式。

5.4.5 泛型工具类

泛型工具类型:TS内置了一些常用的工具类型,来简化TS中的一些常见操作。 说明:它们都是基于泛型实现的(泛型适用于多种类型,更加通用),并且是内置的,可以直接在代码中使用。这些工具类型有很多,主要学习以下几个: Partial<Type>, Readonly<Type>, Pick<Type, Keys>, Record<Keys, Type>

  1. 泛型工具类型-Partial<Type>用来构造(创建)一个类型,将Type的所有属性设置为可选
interface Props{
    id:string
    children:number[]
}
type PartialProps=Partial<Props>

let p1:Props={
    id:'1',
    children:[1,2]
}
let p2:PartialProps={
    id:'',
    children:[1,3]
}

说明:构造出来的新类型Partialprops结构和Props相同,但所有属性都变为可选的。

  1. 泛型工具类型- Readonly<Type>用来构造一个类型,将Type的所有属性都设置为readonly (只读)
interface Props{
    id:string
    children:number[]
}
type ReadonlyProps=Readonly <Props>

let p1:ReadonlyProps={
    id:'',
    children:[1,3]
}
// p1.id='2'//报错,只读,不可修改

说明:构造出来的新类型ReadonlyProps结构和Props相同,但所有属性都变为只读的。当我们想重新给id属性赋值时,就会报错:无法分配到"id",因为它是只读属性。

  1. 泛型工具类型-Pick<Type, Keys> 从 Type中选择一组属性来构造新类型
interface Props{
    id:string
    title:string
    children:number[]
}
type PickProps=Pick<Props,'id'|'title'>

let p1:PickProps
  • Pick工具类型有两个类型变量:1表示选择谁的属性2表示选择哪几个属性。
  • 其中第二个类型变量,如果只选择一个则只传入该属性名即可。
  • 第二个类型变量传入的属性只能是第一个类型变量中存在的属性
  • 构造出来的新类型PickProps,只有id和title两个属性类型。
  1. 泛型工具类型-Record<Keys,Type>构造一个对象类型,属性键为Keys,属性类型为Type
type RecordObj=Record<'a'|'b'|'c',string[]>

// type RecordObj={
//     a:string[]
//     b:string[]
//     c:string[]
// }

let obj:RecordObj={
    a:['a'],
    b:['b'],
    c:['c']
}
  • Record工具类型有两个类型变量:1表示对象有哪些属性2表示对象属性的类型
  • 构建的新对象类型RecordObj表示:这个对象有三个属性分别为a/b/c,属性值的类型都是string[]。

5.5索引签名类型和索引查询类型

绝大多数情况下,我们都可以在使用对象前就确定对象的结构,并为对象添加准确的类型。 使用场景:当无法确定对象中有哪些属性(或者说对象中可以出现任意多个属性),此时,就用到索引签名类型了

interface AnyObject{
    [key:string]:number
}
let obj:AnyObject={
    a:1,
    abc:124,
    asc:1441
}

说明:

  1. 使用[key: string]来约束该接口中允许出现的属性名称。表示只要是string类型的属性名称,都可以出现在对象中。
  2. 这样,对象obj中就可以出现任意多个属性(比如,a、b等)。
  3. key 只是一个占位符,可以换成任意合法的变量名称。
  4. 隐藏的前置知识:JS中对象()的键是string类型的

在JS中数组是一类特殊的对象,特殊在数组的键(索引)是数值类型。 并且,数组也可以出现任意多个元素。所以,在数组对应的泛型接口中,也用到了索引签名类型。

interface MyArray<T>{
    [n: number]: T;
}
let arr:MyArray<number>=[1,2,3]
  1. MyArray接口模拟原生的数组接口,并使用[n:number]来作为索引签名类型。
  2. 该索引签名类型表示:只要是number类型的键(索引)都可以出现在数组中,或者说数组中可以有任意多个元素
  3. 同时也符合数组索引是number类型这一前提。

5.6映射类型

映射类型:基于旧类型创建新类型(对象类型),减少重复、提升开发效率。 比如,类型PropKeys有x/y/z,另一个类型Type1中也有x/y/z,并且Type1 中x/y/z的类型相同:

type PropKey='x'|'y'|'z'
type Type1={x:string,y:string,z:string}

这样书写没错,但x/y/z重复书写了两次。像这种情况,就可以使用映射类型来进行简化。

type PropKey='x'|'y'|'z'
type Type2={[Key in PropKey]:number}

5.6.1 映射类型keyof

  1. 映射类型是基于索引签名类型的,所以,该语法类似于索引签名类型,也使用了[]
  2. Key in PropKeys 表示 Key可以是PropKeys联合类型中的任意一个,类似于forin(let k in obj)。
  3. 使用映射类型创建的新对象类型Type2和类型 Type1结构完全相同。
  4. 注意:映射类型只能在类型别名中使用,不能在接口中使用

映射类型除了根据联合类型创建新类型外,还可以根据对象类型来创建:

type Props={a:number;b:string;c:boolean}

type Type3={[key in keyof Props]:number}

说明:

  1. 首先,先执行keyof Props获取到对象类型Props中所有键的联合类型即,'a'|'b'|'c'。
  2. 然后,Key in...就表示 Key 可以是Props中所有的键名称中的任意一个。

5.6.2 分析泛型工具类型Partial的实现

泛型工具类型(比如,Partial<Type>)都是基于映射类型实现的

 type Partial<T>={
    [P in keyof T]?:T[P]
}

type Props={
    a:number
    b:string
    c:boolean
}
type PartialProps=Partial<Props>
  1. keyof T即 keyof Props表示获取Props 的所有键,也就是: 'a'l 'b' | 'c'。
  2. 在[]后面添加?(问号),表示将这些属性变为可选的,以此来实现Partial的功能。
  3. 冒号后面的T[P]表示获取T中每个键对应的类型。比如,如果是'a'则类型是number;如果是'b'则类型是string。
  4. 最终,新类型Partialprops和旧类型Props结构完全相同,只是让所有类型都变为可选了。

5.6.3 索引查询类型

刚刚用到的T[P]语法,在TS中叫做索引查询(访问)类型

作用:用来查询属性的类型

type Props={
    a:number
    b:string
    c:boolean
}
type TypeA=Props['a']

解释:Props['a']表示查询类型Props 中属性'a'对应的类型number。所以,TypeA的类型为number。

注意:中的属性必须存在于被查询类型中,否则就会报错

索引查询类型的其他使用方式:同时查询多个索引的类型

type Props={
    a:number
    b:string
    c:boolean
}

type TypeA=Props['a'|'b']//number|string

type TypeB=Props[keyof Props]//number|string|boolean
  • 使用字符串字面量的联合类型,获取属性a和 b对应的类型,结果为: string | number。
  • 使用keyof操作符获取Props中所有键对应的类型,结果为: string | number | boolean。

6.TypeScript类型声明文件

概述:

  • 今天几乎所有的JavaScript应用都会引入许多第三方库来完成任务需求。
  • 这些第三方库不管是否是用TS编写的,最终都要编译成JS代码,才能发布给开发者使用。
  • 我们知道是TS提供了类型,才有了代码提示和类型保护等机制。
  • 但在项目开发中使用第三方库时,你会发现它们几乎都有相应的TS类型,这些类型是怎么来的呢?类型声明文件类型声明文件:用来为已存在的JS库提供类型信息。
  • 这样在TS项目中使用这些库时,就像用TS一样,都会有代码提示、类型保护等机制了。

6.1 TS中的两种文件类型

  • .ts 文件:
  1. 既包含类型信息又可执行代码
  2. 可以被编译为.js 文件,然后,执行代码。
  3. 用途:编写程序代码的地方。
  • .d.ts 文件:
  1. 只包含类型信息的类型声明文件。
  2. 不会生成.js文件,仅用于提供类型信息
  3. 用途:为JS提供类型信息。

6.2类型声明文件的使用说明

在使用TS开发项目时,类型声明文件的使用包括以下两种方式:

  • 使用已有的类型声明文件
  • 创建自己的类型声明文件

使用已有的类型声明文件:1内置类型声明文件2第三方库的类型声明文件。 1.内置类型声明文件:TS为JS运行时可用的所有标准化内置API都提供了声明文件。 比如,在使用数组时,数组所有方法都会有相应的代码提示以及类型信息

可以通过Ctrl+鼠标左键(Mac: option+鼠标左键)来查看内置类型声明文件内容。 比如,查看forEach方法的类型声明,在VSCode中会自动跳转到lib.es5.d.ts类型声明文件中。 当然,像window、document等 BOM、DOMAPI也都有相应的类型声明(lib.dom.d.ts)。

2.第三方库的类型声明文件:目前,几乎所有常用的第三方库都有相应的类型声明文件。 第三方库的类型声明文件有两种存在形式:1库自带类型声明文件2由DefinitelyTyped提供。 1.库自带类型声明文件:比如,axios。

image.png 说明:这种情况下,正常导入该库,TS就会自动加载库自己的类型声明文件,以提供该库的类型声明。

如何知道使用index.d.ts作为类型声明文件的呢? package.json的typings或@types配置