TypeScript

  1. 1. TypeScript 简介
    1. 1.1. 什么是 TypeScript
    2. 1.2. 安装 TypeScript
    3. 1.3. 编译并运行 TypeScript
    4. 1.4. 简化运行 TS
  2. 2. TypeScript 基础
    1. 2.1. 原始数据类型
    2. 2.2. 数组类型
    3. 2.3. 联合类型
    4. 2.4. 类型别名
    5. 2.5. 函数类型
      1. 2.5.1. 可选参数
    6. 2.6. 对象类型
      1. 2.6.1. 可选属性
    7. 2.7. 接口
      1. 2.7.1. 接口继承
    8. 2.8. 元组类型
    9. 2.9. 类型推论
    10. 2.10. 类型断言
    11. 2.11. 字面量类型 Literal Types
    12. 2.12. 枚举类型 Enum
      1. 2.12.1. 数字枚举
      2. 2.12.2. 字符串枚举
    13. 2.13. any 类型
    14. 2.14. typeof
  3. 3. TypeScript 进阶
    1. 3.1. 类 Class
      1. 3.1.1. 构造函数
      2. 3.1.2. 实例方法
      3. 3.1.3. 类的继承
      4. 3.1.4. 可见性修饰符
      5. 3.1.5. 参数属性
    2. 3.2. 类型兼容性
      1. 3.2.1. 接口兼容性
      2. 3.2.2. 函数兼容性
    3. 3.3. 交叉类型
    4. 3.4. 泛型 Generics
      1. 3.4.1. 多个类型参数
      2. 3.4.2. 泛型约束
  4. 4. 参考

TypeScript is a strongly typed programming language that builds on JavaScript, giving you better tooling at any scale.

TypeScript 简介

什么是 TypeScript

TypeScript 是 JavaScript 的超集,TS 相比 JS 的优势:

  1. 更早发现错误,减少找/改 BUG 时间,提升开发效率
  2. 程序中任何位置的代码都有代码提示,随时随地的安全感,增强开发体验
  3. 强大的类型系统提升了代码的可维护性,使得重构代码更容易
  4. 支持最新的 ECMAScript 语法,优先体验最新的语法
  5. TS 类型推断机制,不需要在代码中的每个地方都显示标注类型

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

安装 TypeScript

由于 Node.js/浏览器只认识 JS 代码,所以需要将 TS 代码转换为 JS 代码,才能运行。

1
2
# 安装 TypeScript
$ npm install -g typescript

以上命令会在全局环境下安装 tsc 命令,安装完成之后,我们就可以在任何地方执行 tsc 命令了

编译并运行 TypeScript

  1. 创建 hello.ts 文件
1
2
3
4
5
6
function sayHello(person: string) {
return 'Hello, ' + person;
}

let user = 'Zhang';
console.log(sayHello(user));
  1. 将 TS 编译为 JS
1
$ tsc hello.ts
  1. 执行 JS 代码
1
$ node hello.js

简化运行 TS

使用 ts-node 可以直接运行 TS 代码,不需要编译为 JS 代码

1
$ npm install -g ts-node

使用:

1
$ ts-node hello.ts

TypeScript 基础

原始数据类型

JavaScript 的类型

  • 原始数据类型(Primitive data types):BooleanNullUndefinedNumberStringSymbol,以及 ES10 中的新类型 BigInt
  • 原始数据类型(Primitive data types):Object

TypeScript 新增类型

联合类型,自定义类型(类型别名),接口,元组,字面量类型,枚举,void,any 等

1
2
3
4
5
6
7
8
9
let isDone: boolean = false;
let decLiteral: number = 6;
let myName: string = 'Tom';
function alertName(): void {
alert('My name is Tom');
}
let u: undefined = undefined;
let n: null = null;
let s: symbol = Symbol();

数组类型

数组类型有 2 种写法:「类型 + 方括号」表示法和数组泛型(Array Generic)

1
2
let fibonacci: number[] = [1, 1, 2, 3, 5];
let fibonacci: Array<number> = [1, 1, 2, 3, 5];

接口也可以用来描述数组:

1
2
3
4
interface NumberArray {
[index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];

NumberArray 表示:只要索引的类型是数字时,那么值的类型必须是数字

联合类型

1
2
3
4
// 注意小括号
let arr: (string | number)[] = [1, '2', 3];
let arr1: number | string[] = ['a', 'b', 'c'];
let arr2: number | string[] = 123;

类型别名

类型别名(自定义类型):为任意类型起别名

使用场景:当复杂的同一类型被多次使用时,可以通过类型别名,简化该类型的使用

1
2
3
type CustomArray = (number | string)[]
let arr1: CustomArray = [1, '2', 3];
let arr2: CustomArray = ['a', 'b', 6, 7];

函数类型

在 JavaScript 中,有两种常见的定义函数的方式——函数声明(Function Declaration)和函数表达式(Function Expression):

1
2
3
4
5
6
7
8
9
// 函数声明(Function Declaration)
function sum(x, y) {
return x + y;
}

// 函数表达式(Function Expression)
let mySum = function (x, y) {
return x + y;
};

TypeScript 为函数指定类型的两种方式:

  1. 单独指定参数,返回值类型
1
2
3
function add(num1: number, num2: number): number {
return num1 + num2
}
1
2
3
4
// ES6 箭头函数:https://es6.ruanyifeng.com/#docs/function#%E7%AE%AD%E5%A4%B4%E5%87%BD%E6%95%B0
const add = (num1: number, num2: number): number => {
return num1 + num2
}
  1. 同时指定参数,返回值类型
1
2
3
const add: (num1: number, num2: number) => number = (num1, num2) => {
return num1 + num2
}

当函数作为表达式时,可以通过类似箭头函数形式的语法来为函数添加类型
这种形式只适用于函数表达式

可选参数

使用函数实现某个功能时,参数可以传也可以不传。这种情况下,在给函数参数指定类型时,需要使用可选参数

1
2
3
4
5
6
7
8
9
function buildName(firstName: string, lastName?: string) {
if (lastName) {
return firstName + ' ' + lastName;
} else {
return firstName;
}
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');

可选参数只能出现来参数列表的最后,也就是说可选参数后面不允许再出现必需参数了

对象类型

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

1
2
3
4
5
6
7
let person: { name: string; age: number; sayHi(): void } = {
name: 'Zhang',
age: 20,
sayHi() {
console.log('hi')
}
}
  1. 直接使用 {} 来描述对象结构,属性采用属性名:类型的形式;方法采用方法名():返回值类型的形式
  2. 在一行代码中指定对象的多个属性类型时,使用;来分隔
    1. 如果一行代码只指定一个属性类型(通过换行来分隔多个属性类型),可以去掉;
    2. 方法的类型也可以使用箭头函数形式({sayHi: () => void}
1
2
3
4
5
6
7
8
9
10
11
let person: {
name: string
age: number
sayHi: () => void
} = {
name: 'Zhang',
age: 20,
sayHi() {
console.log('hi')
}
}

可选属性

对象的属性或方法也可以是可选的,此时需要用到可选属性

1
2
3
4
// 如果发送 GET 请求,method 属性就可以省略
function myAxios(config: { url: string; method?: string }) {
console.log(config)
}

接口

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

  1. 使用 interface 关键字来声明接口
  2. 接口名称可以是任意合法的变量名称
  3. 声明接口后,直接使用接口名称作为变量的类型
  4. 因为每一行只有一个属性类型,因此属性类型后面没有 ;
1
2
3
4
5
6
7
8
9
10
11
12
13
interface IPerson {
name: string
age: number
sayHi(): void
}

let person: IPerson = {
name: 'Zhang',
age: 20,
sayHi() {
console.log('hi')
}
}
interface 和 type 的对比
  • 相同点:都可以给对象指定类型
  • 不同的:
    • 接口 interface,只能为对象指定类型
    • 类型别名 type,不仅可以为对象指定类型,实际上可以为任意类型指定别名
1
2
3
4
5
6
7
8
9
10
11
12
13
interface IPerson {
name: string
age: number
sayHi(): void
}

type IPerson = {
name: string
age: number
sayHi(): void
}

type NumStr = number | string

接口继承

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

1
2
interface Point2D { x: number; y: number }
interface Point3D { x: number; y: number; z: number }

更好的方式:

1
2
interface Point2D { x: number; y: number }
interface Point3D extends Point2D { z: number }
  1. 使用 extends 关键字实现了接口继承
  2. 继承后拥有了父接口的所有属性和方法

元组类型

使用场景:在地图中,经常用经纬度坐标来标记位置信息,可以使用数组来记录坐标,那么该数组中只有两个元素,并且这两个元素都是数值类型

1
let position: number[] = [39.5427, 116.2317]

使用 number[] 缺点:不严谨,因该类型的数组中可以出现任意多个数字。更好的方式:使用元素

1
let position: [number, number] = [39.5427, 116.2317]

元组类型可以确切地标记出有多少个元素,以及每个元素的类型

类型推论

如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型

发生类型推论的两种场景场景:

  1. 声明变量并初始化时
  2. 决定函数返回值时

这两种情况下,类型注解可以省略不写

1
2
let age = 18 // 推论为 number
function add(num1: number, num2: number) { return num1 + num2 } // 推论为 number

类型断言

类型断言(Type Assertion)可以用来手动指定一个值的类型,类型断言有两种语法:

1
值 as 类型

1
<类型>值

在 tsx 语法(React 的 jsx 语法的 ts 版)中必须使用前者,即 值 as 类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}

function isFish(animal: Cat | Fish) {
if (typeof (animal as Fish).swim === 'function') {
return true;
}
return false;
}

字面量类型 Literal Types

1
2
let str1 = 'Hello TS'
const str2 = 'Hello TS' // 字面量类型,类型为 'Hello TS'

此处的 str2 的类型为 'Hello TS',而不是 string,因为 str2 是一个字面量类型,它的值只能是 'Hello TS',不能是其他值

除字符串外,任意的 JS 字面量(对象,数字等)都可以作为类型使用

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

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

相比于 string 类型,使用字面量类型更近精确,严谨

枚举类型 Enum

枚举(Enum)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等

1
2
3
4
5
6
7
8
enum Direction { Up, Down, Left, Right }

function changeDirection(direction: Direction) {
console.log(direction)
}

// 访问枚举成员
changeDirection(Direction.Up) // 0

数字枚举

枚举成员会被赋值为从 0 开始递增的数字,同时也会对枚举值到枚举名进行反向映射,也可以给枚举中的成员初始化值

1
2
// Down -> 11,Left -> 12,Right -> 13
enum Direction { Up = 10, Down, Left, Right }
1
enum Direction { Up = 2, Down = 4, Left = 8, Right = 16 }

字符串枚举

1
2
3
4
5
6
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT'
}

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

当编译为 JS 代码时,其他类型会在编译时自动移除,但是枚举类型会被保留在 JS 代码中

1
2
3
4
5
6
7
var Direction;
(function (Direction) {
Direction["Up"] = "UP";
Direction["Down"] = "DOWN";
Direction["Left"] = "LEFT";
Direction["Right"] = "RIGHT";
})(Direction || (Direction = {}));

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

any 类型

不推荐使用 any!这会让 TypeScript 失去 TS 类型保护的优势

任意值(Any)用来表示允许赋值为任意类型

1
2
let myFavoriteNumber: any = 'seven';
myFavoriteNumber = 7;

其他隐式具有 any 的情况:1. 声明变量不提供类型也不提供默认值 2. 函数参数不加类型

typeof

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

1
console.log(typeof "Hello world") // string

实际上,TS 也提供了 typeof 操作符:可以在类型上下文中引用变量或属性的类型

使用场景:根据已有变量的值,获取该值的类型,来简化类型书写

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

TypeScript 进阶

类 Class

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

类相关的概念:

  • 类(Class):定义了一件事物的抽象特点,包含它的属性和方法
  • 对象(Object):类的实例,通过 new 生成
  • 面向对象(OOP)的三大特性:封装、继承、多态
    • 封装(Encapsulation):将对数据的操作细节隐藏起来,只暴露对外的接口。外界调用端不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象,同时也保证了外界无法任意更改对象内部的数据
    • 继承(Inheritance):子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性
    • 多态(Polymorphism):由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。比如 Cat 和 Dog 都继承自 Animal,但是分别实现了自己的 eat 方法。此时针对某一个实例,我们无需了解它是 Cat 还是 Dog,就可以直接调用 eat 方法,程序会自动判断出来应该如何执行 eat
  • 存取器(getter & setter):用以改变属性的读取和赋值行为
  • 修饰符(Modifiers):修饰符是一些关键字,用于限定成员或类型的性质。比如 public 表示公有属性或方法
  • 抽象类(Abstract Class):抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现
  • 接口(Interfaces):不同类之间公有的属性或方法,可以抽象成一个接口。接口可以被类实现(implements)。一个类只能继承自另一个类,但是可以实现多个接口

实例属性初始化:

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

构造函数

1
2
3
4
5
6
7
8
9
10
11
12
class Person {
age: number
gender: string

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

const p = new Person(18, 'male')
console.log(p.age, p.gender)
  1. 成员初始化后,才可以通过 this.age 来访问实例成员
  2. 需要为构造函数指定类型注解,否则会被隐式推断为 any;构造函数不需要返回值类型

实例方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Point {
x = 1
y = 2

scale(n: number): void {
this.x *= n
this.y *= n
}
}

const p = new Point()
p.scale(10)
// 10 20
console.log(p.x, p.y)

类的继承

类继承的两种方式:

  1. extends 继承父类
  2. implements 实现接口

JS 中只有 extends,而 implements 是 TS 提供的

1
2
3
4
5
6
7
8
9
10
11
12
13
class Animal {
move() {
console.log('move')
}
}

class Dog extends Animal {
bark() {
console.log('bark')
}
}

const dog = new Dog()
  1. 通过 extends 关键字实现类的继承
  2. 子类 Dog 继承了父类 Animal ,则 Dog 的实例对象就同时具有了父类 Animal 和子类 Dog 的所有属性和方法
1
2
3
4
5
6
7
8
9
10
11
interface ISingable {
name: string
sing(): void
}

class Person implements ISingable {
name = 'Zhang'
sing() {
console.log('sing')
}
}
  1. 通过 implements 关键字让 class 实现接口
  2. Person 类实现 Singable 意味着 Person 类中必须实现 ISingable 接口中的所有方法和属性

可见性修饰符

类成员可见性:可以使用 TS 来控制 class 的方法或属性对于 class 外的代码是否可见

TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 publicprivate 和 protected

  • public:修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
  • private:修饰的属性或方法是私有的,不能在声明它的类的外部访问(对子类和实例对象都不可见)
  • protected:修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的,对实例不可见

参数属性

修饰符和 readonly 还可以使用在构造函数参数中,等同于类中定义该属性同时给该属性赋值,使代码更简洁

只读属性关键字 readonly,只允许出现在属性声明或索引签名或构造函数中,用来防止在构造函数之外对属性进行赋值

1
2
3
4
5
6
class Person {
readonly age: number = 18
constructor(age: number) {
this.age = age
}
}
  1. 使用 readonly关键字修饰该属性是只读的,注意只能修饰属性不能修饰方法
  2. 注意:属性 age 后面的类型注解(比如此处的 number)如果不加,则 age 的类型为 18(字面量类型)
  3. 接口或者 {} 表示的对象类型,也可以使用 readonly
  4. 如果 readonly 和其他访问修饰符同时存在的话,需要写在其后面

类型兼容性

两种类型系统:

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

TS 采用的是结构化类型系统,也叫做 duck typing(鸭子类型),类型检查关注的是值所具有的形状

1
2
3
4
class Point { x: number; y: number }
class Point2D { x: number; y: number }

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

在结构化类型系统中,对于对象类型来说,y 的成员至少与 x 相同。则 x 兼容 y(成员多的可以赋值给少的)

1
2
3
4
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

除了 class 之外,TS 中的其他类型也存在相互兼容的情况,包括

  1. 接口兼容性
  2. 函数兼容性等

接口兼容性

接口之间的兼容性类似于 class,并且 class 和 interface 之间也可以兼容

1
2
3
4
5
6
7
8
9
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
1
2
3
interface Point2D { x: number; y: number }
class Point3D { x: number; y: number; z: number }
let p3: Point2D = new Point3D()

函数兼容性

函数兼容性需要考虑:

  1. 参数个数
  2. 参数类型
  3. 返回值类型

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

1
2
3
4
type F1 = (a: number) => void
type F2 = (a: number, b: number) => void
let f1: F1
let f2: F2 = f1
1
2
3
const arr = ['a', 'b', 'c']
arr.forEach(() => {})
arr.forEach(((item) => {})
  1. 参数少的可以赋值给多的,因此 f1 可以赋值给 f2
  2. 数组 forEach 方法的第一个参数是回调函数,该示例中类型为:(value: string, index: number, array: string[]) => void
  3. 在 JS 中省略用不到的函数参数实际上是很常见的,这样的使用方式,促成了 TS 中函数类型之间的兼容性
  4. 并且因为回调函数是由类型的,所以 TS 会自动推导出参数 item,index,array 的类型

参数类型:相同位置的参数类型要相同(原始类型)或兼容(对象类型)

1
2
3
4
type F1 = (a: number) => string
type F2 = (a: number) => string
let f1: F1
let f2: F2 = f1

函数类型 F2 兼容函数类型 F1,因为 F1 和 F2 的第一个参数类型相同

1
2
3
4
5
6
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 = f2

特别注意,此处与上述提到的接口兼容性冲突,这是参数少的 f2 可以赋值给参数多的 f3

返回值类型:只关注返回值类型本身即可

1
2
3
4
type F5 = () => string
type F6 = () => string
let f5: F5
let f6: F6 = f5
  1. 如果返回值类型是原始类型,此时两个类型要相同
1
2
3
4
5
type F7 = () => { name: string }
type F8 = () => { name: string; age: number }
let f7: F7
let f8: F8
f7 = f8
  1. 如果返回值类型是对象类型,此时成员多的可以赋值给成员少的

交叉类型

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

1
2
3
4
5
6
7
interface Person { name: string }
interface Contact { phone: string }
type PersonDetail = Person & Contact
let obj: PersonDetail = {
name: "Zhang",
phone: "123456"
}

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

1
type PersonDetail = { name: string; phone: string }
交叉类型与接口继承的对比
  • 相同点:都可以实现对象类型的组合
  • 不同点:两种方式实现类型组合时,对于同名属性之间,处理类型冲突的方式不同

泛型 Generics

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性

泛型在**保证类型安全(不丢失类型信息)**的同时,可以让函数等与多种不同的类型一起工作,灵活可复用

1
2
3
4
5
6
7
8
9
10
11
function createArray<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}

return result;
}

createArray<string>(3, 'x'); // ['x', 'x', 'x']
createArray(3, 'x'); // ['x', 'x', 'x'] // 类型推论
  1. 语法:在函数名称的后面添加 <>,尖括号中添加类型变量
  2. 上例中,我们在函数名后添加了 <T>,其中 T 用来指代任意输入的类型,在后面的输入 value: T 和输出 Array<T> 中即可使用了
  3. 接着在调用的时候,可以指定它具体的类型为 string。当然,也可以不手动指定,而让类型推论自动推算出来

多个类型参数

定义泛型的时候,可以一次定义多个类型参数

1
2
3
4
5
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}

swap([7, 'seven']); // ['seven', 7]

泛型约束

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

1
2
3
4
5
6
function loggingIdentity<T>(arg: T): T {
console.log(arg.length);
return arg;
}

// index.ts(2,19): error TS2339: Property 'length' does not exist on type 'T'.

这时,我们可以对泛型进行约束,只允许这个函数传入那些包含 length 属性的变量。这就是泛型约束:

1
2
3
4
5
6
7
8
interface Lengthwise {
length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
  1. 使用 extends 关键字约束了泛型 T 必须符合接口 Lengthwise 的形状,也就是必须包含 length 属性
  2. 此时如果调用 loggingIdentity 的时候,传入的 arg 不包含 length,那么在编译阶段就会报错了

参考