TypeScript基础教程

前言

  • TS是JS的超集,就是说:JS有的,TS也都有。并且TS还包含了类型的概念,将类型不严格的JS变为了类型严格的TS
  • 使用TS能避免很多问题、bug的出现,特别是类型上的问题
  • 个人认为,TS就是和Java差不多的类型规范,对类型要求都非常严格。

常用类型

1、布尔值

最基本的数据类型就是简单的true/false值,在JavaScript和TypeScript里叫做boolean(其它语言中也一样)

在这里插入图片描述

2、数字

和JavaScript一样,TypeScript里的所有数字都是浮点数。 这些浮点数的类型是 number。 除了支持十进制和十六进制字面量,TypeScript还支持ECMAScript 2015中引入的二进制和八进制字面量

在这里插入图片描述

3、字符串

JavaScript程序的另一项基本操作是处理网页或服务器端的文本数据。 像其它语言里一样,我们使用 string表示文本数据类型。 和JavaScript一样,可以使用双引号( ")或单引号(')表示字符串。

在这里插入图片描述

你还可以使用模版字符串,它可以定义多行文本和内嵌表达式。 这种字符串是被反引号包围( ```),并且以${ expr }这种形式嵌入表达式

在这里插入图片描述

4、数组

  • TypeScript像JavaScript一样可以操作数组元素。 有两种方式可以定义数组。
    • 第一种,可以在元素类型后面接上 [],表示由此类型元素组成的一个数组
    • 第二种方式是使用数组泛型,Array<元素类型>(不建议,不常用)

在这里插入图片描述

5、元组

元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。 比如,你可以定义一对值分别为 stringnumber类型的元组。

在这里插入图片描述

当访问一个已知索引的元素,会得到正确的类型:

在这里插入图片描述

当访问一个越界的元素,会报错。push时,会校验数据是否为两个类型的联合类型string|number

在这里插入图片描述

6、枚举

  • enum类型是对JavaScript标准数据类型的一个补充。 像C#等其它语言一样,使用枚举类型可以为一组数值赋予友好的名字。

  • 默认值自动生成只针对数值类型,如果有一个值手动赋值为字符串,那么它下面所有的选项都要手动赋值!

  • 手动赋值只限数值、字符串类型

在这里插入图片描述

默认情况下,0开始为元素编号,后面自动递增。 你也可以手动的指定成员的数值。 例如,我们将上面的例子改成从 1开始编号:

在这里插入图片描述

或者,全部都采用手动赋值:

在这里插入图片描述

枚举类型提供的一个便利是你可以由枚举的值得到它的名字。 例如,我们知道数值为2,但是不确定它映射到Color里的哪个名字,我们可以查找相应的名字:

在这里插入图片描述

7、Any

  • 有时候,我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。
  • 这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。
  • 这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。 那么我们可以使用 any类型来标记这些变量:

在这里插入图片描述

  • 在对现有代码进行改写的时候,any类型是十分有用的,它允许你在编译时可选择地包含或移除类型检查。
  • 你可能认为 Object有相似的作用,就像它在其它语言中那样。
  • 但是 Object类型的变量只是允许你给它赋任意值 - 但是却不能够在它上面调用任意的方法,即便它真的有这些方法:

在这里插入图片描述

当你只知道一部分数据的类型时,any类型也是有用的。 比如,你有一个数组,它包含了不同的类型的数据

在这里插入图片描述

8、Void

  • 某种程度上来说,void类型像是与any类型相反,它表示没有任何类型。 当一个函数没有返回值时,你通常会见到其返回值类型是 void
  • 声明一个void类型的变量没有什么大用,因为你只能为它赋予undefined

在这里插入图片描述

9、Null 和 Undefined

TypeScript里,undefinednull两者各自有自己的类型分别叫做undefinednull。 和 void相似,它们的本身的类型用处不是很大

在这里插入图片描述

strictNullChecks标记

  • 使用 on,当值为 或 时,需要先测试这些值,然后再对该值使用方法或属性, 如果可行,我们始终建议人们在他们的代码库中打开
    • undefined只能赋值给void、它本身
    • null、void只能赋值给它本身
  • 使用 off,可以或仍然可以正常访问的值,以及可以分配给任何类型的属性的值。 这类似于没有 null 检查的语言(例如 C#、Java)的行为方式。

在这里插入图片描述

非空断言运算符(后缀)!

TypeScript 还具有一种特殊的语法,用于在不进行任何显式检查的情况下删除类型和从类型中删除类型。 在任何表达式之后写入实际上是一种类型断言,即该值不是 或:null``undefined``!``null``undefined

在这里插入图片描述

10、Never

  • never类型表示的是那些永不存在的值的类型。
  • 例如, never类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型; 变量也可能是 never类型,当它们被永不为真的类型保护所约束时。
  • never类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是never的子类型或可以赋值给never类型(除了never本身之外)。 即使 any也不可以赋值给never

在这里插入图片描述

11、Object

object表示非原始类型,也就是除numberstringbooleansymbolnullundefined之外的类型。

在这里插入图片描述

12、联合类型

  • 联合类型使用单个管道符|,由两个或多个其他类型组成的类型,表示可以使这些类型中的任意一种。
  • 注意:TS联合类型语法是|,不要与JS的或||混淆了

假设一个数组中既有string类型,又有number类型,那么写法如下

在这里插入图片描述

13、interface

  • interface可以用来描述一个对象里面所有属性的类型
  • 虽然使用type也可以自定义一个对象类型,但是interface可以被继承,并且被继承的类拥有该接口的所有属性与类型)
  • interface也可以被实现(implements),实现的类必须重写该接口的所有方法(后面会将)
  • interface中只能定义方法的(参数数量、类型、返回值)等,不得写任何关于方法的实现(由实现类去实现方法)(后面会讲)

在这里插入图片描述

14、类型推断

  • TS中,某些没有明确指出类型的地方,TS类型推论机制会帮助提供类型
  • 换句话说,由于类型推论的存在,这些地方,类型注解可以省略不写
  • 发生类型推论的2中场景:
    1. 声明变量并初始化时
    2. 决定函数返回值时

在这里插入图片描述

15、typeof

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

在这里插入图片描述

解释

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

16、类型别名(自定义类型)

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

在这里插入图片描述

解释

  1. 使用type关键字来创建类型别名
  2. 类型别名,可以时任意合法的变量名称
  3. 创建类型别名后,直接使用改类型别名作为变量的类型注解即可

也可以在如下情况使用,会有奇效(提示会很智能)

在这里插入图片描述

17、类型断言

  • 有时候你会遇到这样的情况,你会比TypeScript更了解某个值的详细信息。 通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。
  • 通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。 类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript会假设你,程序员,已经进行了必须的检查。
  • 类型断言有两种形式。
    • 其一是“尖括号”语法
    • 另一个为as语法(建议):当你在TypeScript里使用JSX时,只有 as语法断言是被允许的。

在这里插入图片描述

TS编译与运行

上面学习了这么多基本类型,下面我们研究下怎么运行xxx.ts文件

运行ts文件,大概有如下两步:

  • 在这里插入图片描述
  1. 使用tsc xxx.ts命令将ts文件编译为js文件
  2. 使用node xxx.js运行js文件即可
  1. 注释报错代码

  2. 编译

    1. 编译的适合,ts会在终端打印出,目前ts中错误的语法

      在这里插入图片描述

  3. 编译完后,会在同级目录里生成ts文件名.js文件

    在这里插入图片描述

  4. 运行js文件

    在这里插入图片描述

高级类型

TS中高级类型有很多,重点学习以下几个常用高级类型:

  1. class类
  2. interface
  3. 类型兼容性
  4. 交叉类型
  5. 泛型和keyof
  6. 索引签名类型和索引查询类型
  7. 映射类型

1、class

1.1、初始化

  1. 声明成员age, 类型为number(没有初始值)
  2. 声明成员gender,并设置初始值,此时,可省略类型注解(TS类型推论为string类型)
class Persion {
  age: number
  // (property) Persion.gender: string
  gender = '男'
}

1.2、构造函数

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

在这里插入图片描述

1.3、实例方法

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

在这里插入图片描述

1.4、类继承

类继承的两种方式

  1. extends:继承父类
  2. implements:实现接口
    1. 实现的类必须提供接口中所有指定的方法和属性
1.4.1、继承父类
class Animal {
  name: string
  sex: string

  constructor(name: string, sex: string) {
    this.name = name
    this.sex = sex
  }

  eat: (v1: string) => void = (v1) => {
    console.log('吃:' + v1)
  }
}

class Fish extends Animal {
  color: string
  play: (v1: string) => void = (v1) => {
    console.log('玩:' + v1)
  }

  constructor (color: string, name: string, sex: string) {
    super(name, sex)
    this.color = color
  }
}

const f2 = new Fish('红', '小丑', '男')
console.log(f2.name);
console.log(f2.sex);
console.log(f2.color);
f2.eat('鱼食')
f2.play('水')

编译后运行

在这里插入图片描述

1.4.2、实现接口
interface people {
  name: string
  sex: string
  age: number

  eat: (v1: string) => void
}

class Man implements people {
  name: string
  sex: string
  age: number
  eat: (v1: string) => void = (v1) => {
    console.log('吃:' + v1)
  }

  play: (v1: string) => void = (v1) => {
    console.log('玩:' + v1)
  }

  constructor(name: string, sex: string, age: number) {
    this.name = name
    this.sex = sex
    this.age = age
  }
}

const m1 = new Man('张三', '男', 18)
console.log(m1.name);
console.log(m1.sex);
console.log(m1.age);
m1.eat('辣条')
m1.play('足球')

编译后运行

在这里插入图片描述

1.5、属性描述符

  • 属性描述符:可以使用TS控制class的方法或属性对于class外的代码是否可见
  • 属性描述符包括:
    1. public:表示公开的、公有的,公有成员可以被任何地方访问,默认可见性
    1. protected:表示受保护的,仅对其声明所在类和子类中(非实例对象)可见
    1. private:表示私有的,只在当前类中可见,对实例对象以及子类也是不可见的
1.5.1、public
  • 在类属性或方法面前添加public关键字,来修饰该属性或方法事共有的
  • 因为public事默认科技艾女星,所以,可以直接省略
class User {
  public name: string
  sex: string
  age: number

  public eat: (v1: string) => void = (v1) => {
    console.log(`用户${this.name}吃:` + v1)
  }

  constructor(name: string, age: number, sex: string) {
    this.name = name
    this.age = age
    this.sex = sex
  }
}

const u1 = new User('张三', 18, '男')
const u2 = new User('李四', 19, '女')
console.log("u1:", u1.name, u1.age, u1.sex);
console.log("u2:", u2.name, u2.age, u2.sex);
u1.eat('面条')
u2.eat('米饭')

在这里插入图片描述

1.5.2、protected
  • 在类属性或方法前面添加protected关键字,来修饰该属性或方法是受保护的
  • 在子类的方法内部可以通过this来访问父类中受保护的成员,但是对实例不可见!

在这里插入图片描述

1.5.3、private
  • 在类属性或方法前面添加private关键字,来修饰该属性或方法事私有的
  • 私有的属性或方法只能在当前类中可见,对子类和实例对象也都是不可见的!

在这里插入图片描述

1.5.4、readonly
  • 除了可见性修饰符之外,还有一个常见的修饰符就是readonly(只读修饰符)
  • readonly:表示只读,用来防止在构造函数之外对属性进行赋值
  • 使用readonly关键字修饰该属性事只读的,注意只能修饰属性,不能修饰方法
  • 接口或者{}表示的对象类型,也可以使用readonly

在这里插入图片描述

1.5.5、对象可选属性
  • 对象可选属性通过在属性后面添加?,来表示该属性可有可无
  • 接口或者{}表示的对象类型,属性上也都可以使用?
  • 后面演示函数可选参数和当前使用方法基本一致

在这里插入图片描述

2、函数

  • 函数的类型实际上指的是:函数参数返回值的类型
  • 为函数指定类型的两种方式
    1. 单独指定参数、返回值的类型
    2. 同时指定参数、返回值的类型
  • 如果函数没有返回值,那么,函数返回值类型为void

2.1、方式一:单独指定参数、返回值的类型

function add(x: number, y: number): number {
  return x + y
}

const add2 = (x: number, y: number): number => {
  return x + y
}

// function add(x: number, y: number): number 代表:参数x:类型为number,参数y:类型为number,返回值类型为number
add(1,2);
// const add2: (x: number, y: number) => number
add2(2,3);

2.2、方式二:同时指定参数、返回值类型

  • 当函数作为表达式时,可以通过类似箭头函数形式的语法来为函数添加类型
  • 这种形式只适用于函数表达式
const add3: (x: number, y: number) => number = (x, y) => {
  return x + y
}

// const add3: (x: number, y: number) => number
add3(1,3);

2.3、函数可选参数

  • 同上面类属性添加可选参数一样,也是通过在参数属性后面添加==?==来标识该属性是可传可不传的

在这里插入图片描述

3、类型兼容性

类型系统共有两种

  1. Structural Type System (结构化类型系统)
  2. Nominal Type System (标明类型系统)

TS采用的是结构化类型系统,也叫做duct typing (鸭子类型)类型检查关注的是值所具有的形状。也就是说,在结构类型系统中,如果两个对象具有相同的形状,则认为它们属于同一类型。

在这里插入图片描述

解释:

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

3.1、对象之间兼容性

对于对象类型来说,y的成员以及成员类型至少与x相同,则x兼容y==(成员多的可以赋值给成员少的)==

3.1.1、相同属性、数量下
// 相同属性 相同类型下
class point2D {
  x: number
  y: number
}

class point3D {
  x: number
  y: number
}


type type1 = { x: number; y: number }

type type2 = { x: number; y: number }


const v1: point2D = new point3D()
const v2: type1 = new point2D();

const v3: type2 = { x: 1, y: 2 };
const v4: type1 = v3;
3.1.2、不同属性、数量下
// 相同属性 相同类型下
class point2D {
  x: number
  y: number
}

class point3D {
  x: number
  y: number
  z: string
}


type type1 = { x: number; y: number }

type type2 = { x: number; y: string; z: number };


const v1: point2D = new point3D()
// 报错:类型 "point2D" 中缺少属性 "z",但类型 "point3D" 中需要该属性。
const v2: point3D = new point2D();


// 不能将类型“number”分配给类型“string”。
const v3: type2 = { x: 1, y: 2 };

// 不能将类型“type2”分配给类型“type1”。 属性“y”的类型不兼容。不能将类型“string”分配给类型“number”。
const v4: type1 = v3;

3.2、接口之间兼容性

3.2.1、相同属性、数量下
// 相同属性 相同类型下
class point2D {
  x: number
  y: number
}

class point3D {
  x: number
  y: number
}


type type1 = { x: number; y: number }

type type2 = { x: number; y: number }


const v1: point2D = new point3D()
const v2: type1 = new point2D();

const v3: type2 = { x: 1, y: 2 };
const v4: type1 = v3;

interface interfase1 {
  x: number
  y: number
}

interface interface2 {
  x: number
  y: number
}

// interface = interface
const v5: interfase1 = {x: 1, y: 2};
const v6: interface2 = v5;
const v7: interfase1 = v6;

// type {} = interface
const v8: type1 = v5;
3.2.2、不同属性、数量下
// 相同属性 相同类型下
class point2D {
  x: number
  y: number
}

class point3D {
  x: number
  y: number
  z: string
}


type type1 = { x: number; y: number }

type type2 = { x: number; y: string; z: number };


const v1: point2D = new point3D()
// 报错:类型 "point2D" 中缺少属性 "z",但类型 "point3D" 中需要该属性。
const v2: point3D = new point2D();


// 不能将类型“number”分配给类型“string”。
const v3: type2 = { x: 1, y: 2 };

// 不能将类型“type2”分配给类型“type1”。 属性“y”的类型不兼容。不能将类型“string”分配给类型“number”。
const v4: type1 = v3;

interface interfase1 {
  x: number
  y: number
}

interface interface2 {
  x: number
  y: number
  z: string
}

const v5: interfase1 = {x: 1, y: 1};
// 类型 "interfase1" 中缺少属性 "z",但类型 "interface2" 中需要该属性。ts(2741)
const v6: interface2 = v5;

const v7: interface2 = {x: 1, y: 2, z: '3'};
const v8: interfase1 = v7;

// class = interface
const v9: point3D = v7;

3.3、函数之间兼容性

  • 函数之间的兼容性比较复杂,需要考虑
    1. 参数个数
    2. 参数类型
    3. 返回值类型
3.3.1、参数个数

参数多的兼容参数少的==(参数少的可以赋值给参数多的)==

type F1 = (a: number, b: number) => number;
type F2 = (a: number, b: number, c: number) => number;

let f1: F1 = (a, b) => a+b;
let f2: F2 = f1;

// 这样就会多传参数,但是之际调用的是 F1 类型的函数,只会用到前两个参数
console.log(f2(1, 2, 3)); // 3
console.log(f2(1, 2, 5)); // 3

let f3: F2 = (a, b, c) => a+b+c;
// 报错 不能将类型“F2”分配给类型“F1”
let f4: F1 = f3;

// 上面赋值报错了,但是下面调用并没有报错,实际也是不正确的。因为有报错,所以编译也会报错
console.log(f4(1, 2)); // NaN = 1+2+undefined

在这里插入图片描述

3.3.2、参数类型
type F1 = (a: number, b: number) => number;
type F2 = (a: number, b: string) => number;

let f1: F1 = (a, b) => a + b;
// 不能将类型“F1”分配给类型“F2”  参数“b”和“b” 的类型不兼容。 不能将类型“string”分配给类型“number”
let f2: F2 = f1;
3.3.3、返回值类型
type F1 = (a: number, b: number) => number;
type F2 = (a: number, b: number) => string;

let f1: F1 = (a, b) => a + b;

// 不能将类型“F1”分配给类型“F2”。 不能将类型“number”分配给类型“string”
let f2: F2 = f1;

4、交叉类型

  • 交叉类型(&):功能类似于接口继承(extends)用于组合多个类型为一个类型(常用于对象类型)
  • 使用交叉类型后,新的类型就同时具备了类型A、类型B的所有属性类型
type c1 = {
  name: string;
};

type c2 = {
  age: number;
};

// 交叉类型
type user = c1 & c2;

// 相当于如下类型
type user2 = {
  name: string;
  age: number;
};

// 包含属性 name、age
let u: user = {
  name: "张三",
  age: 18,
};

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

  • 相同点:都可以实现对象类型的组合
  • 不同点:两种方式实现类型组合时,对于同名属性之间,处理类型冲突的方式不同
// ------------接口继承
interface i1 {
  name: string;
  age: number;
}

// 拥有相同属性,但是类型不同,直接报错 属性“age”的类型不兼容。 不能将类型“string”分配给类型“number”
interface i2 extends i1 {
  age: string;
}


// ------------交叉类型
interface i3 {
  name: string;
  age: number;
}

interface i4 {
  age: string;
}

type i5 = i3 & i4;

let v1: i5 = {
  name: "张三",
  // age由于i3、i4中都有,ts自动将其类型改为never, 报错:不能将类型“string”分配给类型“never”。
  age: '1',
};

5、泛型

泛型是可以在保证类型安全前提下,让函数等与多种类型一起工作,从而实现复用,常用于:函数、接口、class

5.1、案例

需求:创建一个id函数,传入什么数据就返回该数据本身(也就是说参数和返回值类型相同)

function id(value: number):number {
  return value;
}

// 没问题
id(10);

// 类型“boolean”的参数不能赋给类型“number”的参数。ts(2345)
id(false);

// 于是你会想到 使用 any,可是这样就失去了类型校验
function id2(value) {
  return value;
}

id2(123);

id2(false);

const v1 = id2('123')
// 实际返回的是字符串类型,并没有toFixed方法,但是类型校验也没有报错
v1.toFixed()


// 总不可能把每个类型都写一遍吧,于是,我们可以使用泛型
/**
 * 如下代码,Type 就是我们定义的泛型名称,它可以指代任何类型(使用的时候指定)
 * 如果指定类型为string,那么所有用到Type的地方,都会自动改为string类型
 */
function id3<Type>(value: Type): Type {
  return value;
}


// 不写泛型会进行类型推断,可以省略<boolean>
id3(false)

id3(123)

const v2 = id3<string>('1213')

// 报错;属性“toFixed”在类型“string”上不存在。你是否指的是“fixed”?ts(2551)
v2.toFixed();

5.2、使用讲解

function id<Type>(value: Type): Type{ return value }

  • 语法:在函数名称的后面添加<>(尖括号),尖括号中添加类型变量,比如此处的Type
  • 类型变量Type,是一种特殊类型的变量,它处理类型而不是值
  • 该类型变量相当于一个类型容器,能够捕获用户提供的类型(具体是什么类型由用户调用该函数时指定)
  • 因为Type时类型,因此可以将其作为函数参数和返回值的类型,标识参数和返回值具有相同的类型
  • 类型变量Type,可以时任意合法的变量名称。

5.3、简化调用泛型函数

  • 并不是在调用的时候,每次都要写<类型>,那样会很麻烦。只有当编译器无法推断类型或者推断的类型不准确时,就需要显式的创建类型函数
  • 在调用泛型函数时,可以省略<类型>来简化泛型函数的使用,上面5.1也有演示
  • 此时,TS内部会采用一种叫做类型参数推断的机制,来辊距传入的实参自动推断出类型变量Type的类型
  • 比如:传入实参10,TS会自动推断出变量value的类型number,并作为Type的类型

推荐:使用这种简化的方式调用泛型函数,时代码更短,更易于阅读。

5.4、泛型约束

默认情况下,泛型函数的类型变量Type可以代表多个类型,这导致无法访问任何属性。

function f1<Type>(value: Type): number {
  // 类型“Type”上不存在属性“length”。ts(2339)
  return value.length;
}

方法一:指定更加具体的类型

  • 缺点:使用时只能传入数组参数
function f1<Type>(value: Type[]): number {
  // 将参数改为数组后,数组是有length属性的,所有不报错了
  return value.length;
}

// 但是使用的时候就有限制了,必须传入的时数组
f1([1, 2, 3]);

// 类型“string”的参数不能赋给类型“unknown[]”的参数。ts(2345)
f1('123');

方法二:添加约束(常用)

interface i1 {
  length: number;
}

/**
 * 
 * @param value 限制类型必须继承i1,有用length属性
 * @returns 
 */
function f1<Type extends i1>(value: Type): number {
  // 将参数改为数组后,数组是有length属性的,所有不报错了
  return value.length;
}

// 但是使用的时候就有限制了,必须传入的时数组
f1([1, 2, 3]);

// 类型“string”的参数不能赋给类型“unknown[]”的参数。ts(2345)
f1('123');

// 报错:由于number没有length属性,所以报错
f1(123);

5.5、多个泛型

泛型的类型变量可以有多个,并且类型变量之间还可以约束(比如第二个类型时受第一个类型变量约束)

比如:创建一个函数来获取对象中属性的值

  • 添加了第二个变量K,两个类型变量之间使用(,)英文逗号分割
  • keyof关键字接受一个对象类型】生成其键名称(可能是字符串或数字)的联合类型
  • 本实例中keyof T实际上获取的是x对象所有键的联合类型,也就是'a|b|c|d'
  • 类型变量KT约束,可以理解为:K只能是T所有键中的任意一个,或者说只能访问对象中存在的数组
/**
 * 两个变量T、K之间用 , 分割
 * 类型 K 通过关键字 keyof 限制为 obj 的属性
 * @param obj
 * @param key 
 * @returns 
 */
function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a");

// 报错:类型“"m"”的参数不能赋给类型“"a" | "b" | "c" | "d"”的参数。ts(2345)
getProperty(x, "m");

5.6、泛型接口

接口也可以配合泛型来使用,以增加其灵活性,增强其复用性。

  • 在接口名称的后面添加<类型变量>,那么,这个接口就变成了泛型接口
  • 接口的类型变量,对接口中所有其他成员可见,也就是接口中所有成员都可以使用类型变量
  • 使用泛型接口是,需要显式指定具体的类型
  • 此时,id方法的参数和返回值类型都说number,ids方法的返回值类型式number[]
interface IdFunc<Type> {
  id: (value: Type) => Type;
  ids: () => Type[];
}

// 必须显式指定类型,不可省略
let obj: IdFunc<number> = {
  id: (value: number) => value,
  ids: () => [1, 2, 3],
};

// 报错:泛型类型“IdFunc<Type>”需要 1 个类型参数。ts(2314)
let obj2: IdFunc = {
  id: (value: number) => value,
  ids: () => [1, 2, 3],
};

5.7、js泛型函数

  • 其实JS中的数组在TS中就是一个泛型接口
  • 当我们在使用数组时,TS会根据数组的不同类型,来自动将类型变量设置为相应的类型
  • VS code中,我们把鼠标放到forEach函数上,会给我们提示出来该函数对应的类型定义
const s1 = ['1', '2']
// (method) Array<string>.forEach(callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any): void
s1.forEach(item => item);

const n1 = [1, 2]
// (method) Array<number>.forEach(callbackfn: (value: number, index: number, array: number[]) => void, thisArg?: any): void
n1.forEach(item => item);

5.7、泛型类

class也可以配合泛型来使用

  • 类似于泛型接口,在class名称后面添加<类型变量>,这个类就变成了泛型类
  • 在创建class实例时,在类目后面通过<类型>来制定明确的类型
class c1<T> {
    name: T;
    constructor(name: T) {
        this.name = name;
    }
}

const obj = new c1<string>('xiaoming');

// 报错:类型“number”的参数不能赋给类型“string”的参数。ts(2345)
const obj2 = new c1<string>(123);

5.8、内置泛型工具

  • ==泛型工具类型:==TS内置了一些常用的工具类型,来简化TS中的一些常见操作
  • 它们都说基于泛型实现的(泛型适用于多种类型,更加通用),并且是内置的,可以直接在代码中使用。
  • 这些工具类型很多,主要学习以下几个
    • Partial<Type>:用来构造(创建)一个类型,将Type的所有属性设置为可选
    • Readonly<Type>:用来构造一个类型,将Type的所有属性都设置为readonly(只读)
    • Pick<Type,Keys>:从Type中选择一组属性来构造新类型
    • Record<Keys,Type>:构造一个对象类型,属性键为Keys,属性类型为Type
5.8.1、Partial

用来构造(创建)一个类型,将Type的所有属性设置为可选

  • 构造出来的新类型
type props = {
  name: string,
  age: number
}

/*
  type PartialProps = {
    name?: string | undefined;
    age?: number | undefined;
  }
*/
type PartialProps = Partial<props>

const obj2: PartialProps = {
  name: 'kobe'
}

const obj3: PartialProps = {
  age: 18
}

const obj4: PartialProps = {
  name: 'kobe',
  age: 18
}

const obj5: PartialProps = {
  name: 'kobe',
  age: 18,
  // 报错:对象字面量只能指定已知属性,并且“sex”不在类型“Partial<obj1>”中。ts(2353)
  sex: '男'
}
5.8.2、Readonly

用来构造一个类型,将Type的所有属性都设置为readonly(只读)

  • 构造出来的新类型ReadonlyProps
type props = {
  name: string,
  age: number
}

/*
  type PartialProps = {
    readonly name: string;
    readonly age: number;
  }
*/
type PartialProps = Readonly<props>

const obj2: PartialProps = {
  name: 'kobe',
  age: 18
}

// 无法为“name”赋值,因为它是只读属性。ts(2540)
obj2.name = 'james'

5.8.3、Pick

从Type中选择一组属性来构造新类型

  • Pick工具类型有两个类型变量:1:表示选择谁的属性、2:表示选择哪几个属性
  • 其中第二个类型变量,如果只选择一个则之传入该属性名即可
  • 第二个类型变量出入的属性只能是第一个变量中存在的属性
  • 构造出来的新类型PickProps,只有idtitle两个属性类型
interface Props {
  id: string
  title: string
  children: number[]
}

/*
  type PickProps = {
    id: string;
    title: string;
  }
*/
type PickProps = Pick<Props, 'id' | 'title'>

const obj1: PickProps = {
  id: '123',
  title: '标题'
}

const obj2: PickProps = {
  id: '123',
  title: '标题',
  // 报错:对象字面量只能指定已知属性,并且“children”不在类型“PickProps”中。ts(2353)
  children: [1, 2, 3]
}
5.8.4、Record

构造一个对象类型,属性键为Keys,属性类型为Type

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

const obj1: RecordObj = {
  a: ['1', '2'],
  b: ['3', '4'],
  c: ['5', '6'],
}

const obj2: RecordObj = {
  a: ['1', '2'],
  // 报错:不能将类型“number”分配给类型“string”。ts(2322)
  b: [3, 4],
  // 报错:对象字面量只能指定已知属性,并且“d”不在类型“RecordObj”中。ts(2353)
  d: ['5', '6'],
}

6、索引签名类型

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

6.1、配合对象使用

  • 使用==[key:string]==来约束该接口中允许出现的属性名称。表示只要是string类型的属性名称,都可以出现在对象中
  • 对象obj中就可以出现任意多个属性(比如:a、b),但是值只能为number类型
  • key只是一个占位符,可以换成人以合法的变量名称
  • JS中对象({})的键是string类型的
interface NumberObject {
  [key: string]: number;
}

let obj: NumberObject = {
  name: 123,
  age: 18,
};

// 报错:不能将类型“string”分配给类型“number”。ts(2322)
obj.sex = '男';

6.2、配合数组使用

  • JS中数组是一类特殊的对象,特殊在数组的键(索引)是数值类型
  • 并且,数组也可以出现任意多个元素。。所以,在数组对应的泛型接口中,也用到了索引签名类型
  • MyArray接口模拟原生的数组接口,并使用[index:number]来作为索引签名类型。
  • 该索引签名类型表示:只要是number类型的键(索引)都可以出现在数组中,或者说数组中可以有任意多个元素
  • 同时也符合数组索引时number类型这一前提
interface MyArray<T> {
  [index: number]: T
}

let arr: MyArray<string> = ['a', 'b', 'c']

let arr2: MyArray<number> = [1, 2, 3]

7、映射类型

映射类型:基于旧类型创建新类型(对象类型),减少重复,提升开发效率

7.1、根据联合类型床

比如:类型PropKeysx/y/z,另一个类型Type1中也有x/y/z,并且Type1x/y/z的类型相同

  • 映射类型是基于索引签名类型的,所以,改语法类似于索引签名类型,也使用了[]
  • Key in PropKeys表示Key可以是PropKeys联合类型中的任意一个,类似于forin (let k in obj)
  • 使用映射类型创建的新对象类型Type2和类型Type1结构完全相同
  • 注意:映射类型只能在类型别名中使用,不能在接口中使用
type PropKeys = 'x' | 'y' | 'z';
type Type1 = { x: number; y: number; z: number }

// 上面写法没错,但是 x、y、z 书写了两次,像这样的情况,就可以使用映射类型来进行简化
type Type2 = { [Key in PropKeys]: number }

7.2、根据对象类型创建

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

  • 首先,先执行keyof Props获取到对象类型Props中所有键的联合类型,即'a' | 'b' | 'c'
  • 然后,key in ... 就表示key可以是Props中所有的键名称中的任意一个
type Props = {a: string, b: number, c: boolean}

/**
 * type Props2 = {
    a: string;
    b: string;
    c: string;
  }
 */
type Props2 = { [key in keyof Props]: string }

7.3、分析泛型工具类型实现

实际上,上面讲到的泛型工具类型(比如:Partial<Type>)都是基于映射类型实现的

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

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

/**
 * type PartialProps = {
    a?: number | undefined;
    b?: string | undefined;
    c?: boolean | undefined;
  }
 */
type PartialProps = Partial<Props>

8、索引查询类型

上面用到的T[P]语法,在TS中叫做索引查询(访问)类型:用来查询属性的类型

8.1、查询一个类型

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

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

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

8.2、查询多个类型

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

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

9、类型声明文件

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

9.1、TS中的两种文件类型

  • .ts文件:
    1. 既包含类型信息又可执行代码
    2. 可以被编译为.js文件,然后,执行代码
    3. 用途:编写程序代码的地方。
  • .d.ts文件:
    1. 只包含类型信息的类型声明文件
    2. 不会生成.js文件,仅用于提供类型信息
    3. 用途:为JStigong leix xinxi
  • 总结:.tsimplementation(代码实现文件).d.tsdeclaration(类型声明文件)
  • 如果要为JS库提供类型信息,要使用.d.ts文件。

9.2、类型声明文件的使用说明

  • 使用已有的类型声明文件

    1. 内置类型声明文件

      1. TS为JS运行时可用的所有标准化内置API都提供了声明文件,比如在使用数组时,数组所有方法都会有相应的代码提示及类型信息,按住ctrl键,点击forEach函数,即可跳转至lib.es5.d.ts目录里

        在这里插入图片描述

  1. 第三方库的类型声明文件,很多库里都会包含一个主类型声明文件,像axios,这样在ts项目里,调用第三方库提供的函数,就会有相应的类型提示了

    在这里插入图片描述

 1. 通过查看`package.json`文件,查找`types或typings`属性,即可查看该库的主类型声明文件

    ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/ff31b4b309ea4729b7bb0f64c565a675.png#pic_center)
  1. 上面是第三方库提供了类型声明文件的情况,但是很多老的库是没有提供的,或者有的库是没有使用ts的,那么我们可以进入DefinitelyTyped/DefinitelyTyped: The repository for high quality TypeScript type definitions. (github.com)

9.3、DefinitelyTyped

  • DefinitelyTyped是一个github仓库,用来提供高质量TypeScript类型声明

  • 地址:DefinitelyTyped

  • 可以通过npm/yarn来下周该仓库提供的TS类型声明包,这些包的名称个数为:@types/*,比如:@types/react、@types/lodash

  • 在实际开发时,如果使用的第三方库没有自带的声明文件,VSCode会给出报错的提示。

    在这里插入图片描述

  • 这时候执行命令npm i @types/lodash命令,下载好lodash库的类型文件后,报错会自动消失,并且使用方法包含类型提示

    在这里插入图片描述

  • 所有安装的类型声明文件,全部在node_modules->@types目录中

    在这里插入图片描述

  • 当安装@types/*类型声明包后,TS也会自动加载该类声明包,以提供该库的类型声明。

9.4、declare关键字

declare:用于声明类型、为其它地方(比如.js文件)已存在的变量声明类型,而不是创建一个新的变量

  1. 对于type、interface等这些明确就是TS类型的(只能在TS中使用的),可以省略declare关键字。
  2. 对于let、function等具有双重含义(在JS、TS中都能用),应该使用declare关键字,明确指定此处用于类型声明

在这里插入图片描述

9.5、创建自己的类型声明文件

  • 有两点需求可以创建自己的类型声明文件
    1. 项目内共享类型
    2. 为已有的JS文件提供类型声明
  • 项目内共享类型:如果多个.ts文件中都用到同一个类型,此时可以创建.d.ts文件提供该类型,实现类型共享
  • 为已有JS文件提供类型声明操作步骤:
    1. 创建一个utils.js文件(给原有js文件添加类型声明的文件),然后编写几个简单的工具函数
    2. 编写同名utils.d.ts类型声明文件,然后使用declare关键字编写类型生民即可
    3. 在需要使用utils工具类的.ts文件中,通过import导入即可(.d.ts后缀导入时,直接省略)
9.5.1、创建utils.js文件
export let timeout = 1000;
export let baseUrl = "http://localhost:3000";

export function add(a, b) {
  return a + b + timeout;
}

export function subtract(a, b) {
  return a - b - timeout;
}

export const getFullPath = (path) => {
  return baseUrl + path;
}
9.5.2、编写utils.d.ts类型声明文件
  • 类型声明文件中,一定要导出给js中定义的类型 同名类型声明
// 已经存在的变量,使用declare 声明
declare let timeout: number;
declare let baseUrl: string;

// 类型也可以导出
export type fullPathFun = (path: string) => string

export declare function add(a: number, b: number): number;

export declare function subtract(a: number, b: number): number;

export declare const getFullPath: fullPathFun;

// export { add, subtract, getFullPath };
9.5.3、使用
// 必须省略文件尾缀 .d.ts
import { timeout, baseUrl ,add, subtract, getFullPath, fullPathFun } from "./utils"

// 由于有基本类型推断,可以省略不写这两个属性的类型
console.log(timeout);
console.log(baseUrl);

console.log(add(1, 2))
console.log(subtract(1, 2))
console.log(getFullPath("/a/b/c"))

/*
  未写utils.d.ts前:函数也可以随意调用 虽然执行会报错
  写了后报错:类型“boolean”的参数不能赋给类型“number”的参数。ts(2345)
*/
add(1, false)
// 写后报错:类型“string”的参数不能赋给类型“number”的参数。ts(2345)
subtract("132", 123);

// 使用.d.ts导出的类型
const getFullPath2: fullPathFun = (path: string) => {
  return baseUrl + path
}

console.log(getFullPath2("/c/d/e"));

10、VS Code使用技巧

  1. 鼠标放到函数/定义的属性上会显示对应的类型定义
  2. 按住ctil键,点击调用的方法,会跳转到对应的类型定义文件里
  • 9
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值