导读
本文为笔者ts的学习总结笔记,对ts中各种概念进行了提炼,以及配合了一些代码demo。
大家可以通过此网站来练习ts写法 www.typescriptlang.org/play
本文将按下面概念来分别介绍ts的用法
- 一.类型声明
- 1.type
- 2.interface
- 二.类型推断
- 三.类型分类
- 1.基本类型
- 2.值类型
- 3.any类型
- 4.unknown类型
- 5.never类型
- 6.数组类型
- 7.元祖类型
- 8.枚举类型
- 9.函数类型
- 10.对象类型
- 11.class类
- 四.泛型
- 五.类型运算
- 1.交叉
- 2.联合
- 3.类型缩小
- 4.类型兼容
- 六.类型运算符
- 1.typeof运算符
- 2.keyof运算符
- 3.readonly
- 4.?
- 5.in
- 6.方括号运算符
- 7.extends
- 8.infer
- 9.is
- 10.模板字符串
- 七.断言
- 八.类型工具
- 1.Awaited
- 2.ReturnType
- 3.Parameters
- 4.ThisParameterType
- 5.InstanceType
- 6.ConstructorParameters
- 7.ThisType
- 8.OmitThisParameter
- 9.Exclude<UnionType, ExcludedMembers>
- 10.Extract<Type, Union>
- 11.Pick<Type, Keys>
- 12.Omit<Type, Keys>
- 13.NonNullable
- 14.Readonly
- 15.ReadonlyArray
- 16.Partial
- 17.Required
- 18.Record<Keys, Type>
- 19.Uncapitalize
- 20.Capitalize
- 21.Uppercase
- 22.Lowercase
- 类型映射
- 装饰器
- 模块
- namespace
- declare
- .d.ts
- tsconfig
一.类型声明
介绍:用来标注js变量的类型,变量的值应该与声明的类型一致,如果不一致,TypeScript 就会报错
基本使用:冒号+类型
let foo:string;
function toString(num: number): string {
return String(num)
};
let obj: {
a: string;
b: number;
c: boolean[]
};
复杂类型的声明
有的时候,类型比较复杂,这时候我们可以用type或者interface将类型提炼出来方便复用
1.type
定义一个类型的别名。
-
type
可以声明所有类型type FnType = (x: string) => void; // 声明函数类型 const fn: FnType = function(x) { console.log(x) }; type MyObjType = { // 声明对象类型 a: string; b: number; c: any; addB(x: number): number; } const obj: MyObjType = { a: 'hello', b: 1, c: [1, 2, 3], addB(x) { this.b += x; return this.b; } } type MyString = 'hello word'; // 声明字面量类型 const a: MyString = 'hello word'; // ...略 -
type不允许重名。
type Color = 'red'; type Color = 'blue'; // 报错 -
type的作用域是块级作用域。这意味着,代码块内部定义的别名,影响不到外部。
type Color = 'red'; if (Math.random() < 0.5) { type Color = 'blue'; } -
type支持使用表达式
type Greeting = 'hello'; type Greeting2 = `${Greeting} word`; // 'hello word'
2.interface
用来声明对象模板,它的成员有五种形式
- 对象属性
- 属性索引
- 对象方法
- 函数
- 构造函数
interface Person {
age: number;
getAge(): number;
[Prop: String]: any;
}
const xiaoming: Person = {
age: 18;
getAge() {
return this.age;
};
name: '小明';
}
interface Fn {
(x: string): void;
}
const log: Fn = (x) => {
console.log(x)
}
继承
- 通过
extends关键字实现 - 如果存在同名属性,则同名属性类型必须相同
- 也可以继承type, class类型
interface Style {
color: string;
}
interface Shape {
name: string;
}
interface Circle extends Style, Shape {
radius: number;
}
多个同名接口会合并
- 同名接口合并时,不能出现属性类型冲突
- 同名接口的方法冲突时会发生方法重载
interface Document { createElement(tagName: any): Element; } interface Document { createElement(tagName: "div"): HTMLDivElement; createElement(tagName: "span"): HTMLSpanElement; } interface Document { createElement(tagName: string): HTMLElement; createElement(tagName: "canvas"): HTMLCanvasElement; } // 等同于 interface Document { createElement(tagName: "canvas"): HTMLCanvasElement; createElement(tagName: "div"): HTMLDivElement; createElement(tagName: "span"): HTMLSpanElement; createElement(tagName: string): HTMLElement; createElement(tagName: any): Element; }
interface作为联合类型时,同名属性也是联合类型
interface Circle {
area: bigint;
}
interface Rectangle {
area: number;
}
declare const s: Circle | Rectangle;
s.area; // bigint | number
与type的区别
type可以定义所有类型,interface只能定义对象类型(对象,函数,数组)- interface可以继承extends,type要想继承往往采用
& (类型交叉)方式 - 同名interface会合并,type会报错
- interface不包含属性映射,type可以
- this只能用于interface
- interface无法表达交叉或者联合类型
interface Point {
x: number;
y: number;
}
// 正确
type PointCopy1 = {
[Key in keyof Point]: Point[Key];
};
// 报错,属性映射
interface PointCopy2 {
[Key in keyof Point]: Point[Key];
};
二.类型推断
- 类型声明不是必须的,如果没有声明,ts会自动推断类型
let foo = 123; // 推断foo为number类型 foo = 'hello'; // 赋值为其他类型就会报错 - 如果无法推断,则认为该变量是any类型
let a = undefined; // any const b = undefined; // any let c = null; // any const d = null; // any
三.类型分类
ts类型可以分为基本类型、值类型、any类型、unknown类型、never类型、class类型、枚举、元祖、数组、函数等,下面将会分别介绍
1.基本类型
- boolean
- string
- number
- symbol
- bigint
- object
- null
- undefined
1.boolean只有true和false两种值
const x: boolean = true;
const y: boolean = false;
2.string类型包含所有字符串
const x: string = "hello";
const y: string = `${x} world`;
3.number包含所有整数和浮点数
const num1: number = 123;
const num2: number = 0.1;
const num3: number = 0xffff;
4.bigint包含所有大整数,且与number不兼容
let x: bigint = 123n
let y: bigint = oxffffn;
x = 123; // 报错
y = 204n; // 正确
5.symbol包含所有的Symbol值
const x: symbol = Symbol();
6.object包含了所有的对象,函数,数组
const x: object = { foo: 123 };
const y: object = [1, 2, 3];
const z: object = (n: number) => n + 1;
7.undefined,null
undefined和null是两个特殊的类型,他们既是ts里面的类型,也是js的值
const x: undefined = undefined;
const y: null = null;
任何其他类型的变量都可以赋值为undefined或null
let age: number = 24;
age = null; // 正确
age = undefined; // 正确
const obj: object = undefined;
obj.toString(); // 编译不报错,运行就报错
一个没有被声明类型的变量被赋予undefined和null,则他们会被认为any类型
let a = undefined; // any
const b = undefined; // any
let c = null; // any
const d = null; // any
如果希望避免上面这两种情况,可以打开编译选项strictNullChecks
其他类型
2.值类型
ts规定,单个值也是一种类型,称为值类型
let x: "hello";
x = "hello"; // 正确
x = "world"; // 报错
遇到const 声明的变量,如果没有类型声明,则会推断为值类型
// x 的类型是 "https"
const x = "https";
// z 的类型是 { foo: number }
const z = { foo: 1 };
3.any类型
any称为所有类型的全集,称为顶层类型(top type)
- any类型表示没有任何限制
- any类型的变量可以被赋予任意值
- any类型的变量也可以赋给其它类型
let x: any; x = 'hello'; // 正确 let y: number = 123 y = x; // 正确 - any会关闭类型检查
let x: any; x = undefined; x.toString(); // 正确 - any除了关闭类型检查,还会污染其它变量的类型检查
let x:any = 'hello'; let y:number; y = x; // 不报错 y * 123 // 不报错 y.toFixed() // 不报错
4.unknown类型
为了解决any类型污染其它变量的问题,ts3引入了unknown类型; 它被视为除了any的全集,也属于顶层类型
- 与any的相似
- 该类型的变量可以被赋予任意类型值
let x: unknown; x = 123; x = 'hello' - 与any的不同
- 不能直接调用变量的属性和方法
let v1:unknown = { foo: 123 }; v1.foo // 报错 let v2:unknown = 'hello'; v2.trim() // 报错 let v3:unknown = (n = 0) => n + 1; v3() // 报错 let x:unknown; x = 'hello'; x.trim() // 报错- 该类型的变量不能直接赋予其它变量(除了any和unknown)
let v:unknown = 123; let v1:boolean = v; // 报错 let v2:number = v; // 报错- 只能进行有限的运算操作 unknown能够进行的运算有限,只能进行比较运算(运算符==、===、!=、!==、||、&&、?)、取反运算(运算符!)、typeof运算符和instanceof运算符
Q: 那么如何使用unknown类型的变量了?
A: 进行类型缩小
let a:unknown = 1;
if (typeof a === 'number') {
let r = a + 10; // 正确
}
5.never类型
- 唯一一个底层类型
- never类型的变量可以赋值给其它类型
function f():never {
throw new Error('Error');
}
let v1:number = f(); // 不报错
let v2:string = f(); // 不报错
let v3:boolean = f(); // 不报错
- 不能never类型变量赋予任何值
6.数组类型
数组有一个根本特征:所有成员的类型必须相同,但是成员数量是不确定的,可以是无限数量的成员,也可以是零成员。
-
数组类型的写法
- 类型:[]
let arr: string[] = ['h']- Array接口
let arr: Array<number> = [1, 2] -
联合类型写法
let arr: (string|number)[] = [1,2,3]
let arr2: Array<string|number> = [1,2,3]
-
类型推断
- 当空数组没有声明任何类型时,为这个数组赋值时,类型会不断进行更新
const arr = []; arr; // 推断为 any[] arr.push(123); arr; // 推断类型为 number[] arr.push("abc"); arr; // 推断类型为 (string|number)[]- 非空数组推断会固定类型
const arr = [1]; // 推断为number[]类型 arr.push('a'); // 报错 -
只读数组
- 说明:不允许改变数组成员
- 用法
- 加readonly关键字
- ReadonlyArray
- const 断言
// const arr: ReadonlyArray<number> = [1,2,3];
// const arr: number[] = [1,2,3] as const;
const arr: readonly number[] = [1,2,3];
arr.push(4); // 报错
arr[0] = 4; //报错
7.元祖类型
元组(tuple)是 TypeScript 特有的数据类型,JavaScript 没有单独区分这种类型。它表示成员类型可以自由设置的数组,即数组的各个成员的类型可以不同。
-
必须声明每个成员类型
const s: [string, string, boolean] = ["a", "b", true]; -
元组成员的类型可以添加问号后缀(
?),表示该成员是可选的let a: [number, number?] = [1]; -
使用拓展运算符可以表示不限数量的元祖,可以在任意位置使用
type tuple = [number, ...number[]] -
只读元祖
// 写法一 type t = readonly [number, string]; // 写法二 type t = Readonly<[number, string]>;只读元组是元组的父类型。所以,元组可以替代只读元组,而只读元组不能替代元组。
type t1 = readonly [number, number]; type t2 = [number, number]; let x: t2 = [1, 2]; let y: t1 = x; // 正确 x = y; // 报错- 只元祖造成的问题
function distanceFromOrigin([x, y]:[number, number]) { // return Math.sqrt(x**2 + y**2); } let point = [3, 4] as const; // 只读元祖 distanceFromOrigin(point); // 报错,因为父类型的值不可以赋给子类型变量 distanceFromOrigin(point as [number, number]) // 正确 -
成员数量的推断
- 如果
没有可选成员和扩展运算符,TypeScript 会推断出元组的成员数量(即元组长度)。
function f(point: [number, number]) { if (point.length === 3) { // 报错 // ... } } - 如果
8.枚举类型
-
基本使用,成员只能为number或者string类型
enum Color { Red, Green, Blue, } // 等同于 enum Color { Red = 0, Green = 1, Blue = 2, }- 默认为number,从0开始递增,也可以是任意数值,成员的值可以相同
- 同名enum会合并
- 合并时只允许其中一个首成员省略初始值
- 不能出现同名成员
- 只能都为const枚举或者非const枚举
-
字符串枚举
- 基本使用
enum Direction { Up = "UP", Down = "DOWN", Left = "LEFT", Right = "RIGHT", }- 字符串枚举的所有成员值,都必须显式设置。如果没有设置,成员值默认为数值,且位置必须在字符串成员之前
enum Foo { A, // 0 B = "hello", C, // 报错 }- 变量类型如果是字符串 Enum,就不能再赋值为字符串,这跟数值 Enum 不一样。
enum MyEnum { One = "One", Two = "Two", } let s = MyEnum.One; s = "One"; // 报错 -
数值 Enum 存在反向映射,即可以通过成员值获得成员名。
enum Weekdays { Monday = 1, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday, } console.log(Weekdays[3]); // Wednesday -
遍历
- keyof 返回属性名组成的联合类型
enum MyEnum { A = "a", B = "b", } // 'A'|'B' type Foo = keyof typeof MyEnum;- in 返回属性值
enum MyEnum { A = "a", B = "b", } // { a:any, b: any } type Foo = { [key in MyEnum]: any };
9.函数类型
- 函数的类型声明,需要在声明函数时,给出参数的类型和返回值的类型(可不写,ts会推断),它有三种实现方式
// 1.直接为函数本身标注类型 function fn(x:string):void {xxx}; // 2.箭头函数写法 type Fn = (x: string) => void; // 3.对象写法 type Fn = { (x: string): void } - 参数
- 可选参数,只能声明在参数尾部
type Fn = (x?: string) => void - 参数默认值
type Fn = (x: string) => void` const fn: Fn = (x = 'hello') => {} - 参数解构
function sum( { a, b, c }: { a: number; b: number; c: number } ) { console.log(a + b + c); } - rest参数
function multiply(n:number, ...m:number[]) { return m.map((x) => n * x); }
- 可选参数,只能声明在参数尾部
- 返回值
- void类型
- 没有返回
- 允许undefined
- 允许null (strictNullChecks 需关闭)
- 需要特别注意的是,如果变量、对象方法、函数参数的类型是 void 类型的函数,那么并不代表不能赋值为有返回值的函数。恰恰相反,该变量、对象方法和函数参数可以接受返回任意值的函数,这时并不会报错。
type voidFunc = () => void; const f:voidFunc = () => { return 123; }; - never类型: 表示肯定不会返回值,抛错的函数或者无限执行的函数
- void类型
函数重载有些函数可以接受不同类型或不同个数的参数,并且根据参数的不同,会有不同的函数行为。这种根据参数类型不同,执行不同逻辑的行为,称为函数重载(function overload)。function reverse(str: string): string; function reverse(arr: any[]): any[]; function reverse(stringOrArray: string | any[]): string | any[] { if (typeof stringOrArray === "string") return stringOrArray.split("").reverse().join(""); else return stringOrArray.slice().reverse(); }- 函数重载的每个类型声明之间,以及类型声明与函数实现的类型之间,不能有冲突
- 重载声明的排序很重要,因为 TypeScript 是按照顺序进行检查的,一旦发现符合某个类型声明,就不再往下检查了,所以类型最宽的声明应该放在最后面,防止覆盖其他类型声明。
- 函数重载也可以用来精确描述函数参数与返回值之间的对应关系
type CreateElement = { (tag:'a'): HTMLAnchorElement; (tag:'canvas'): HTMLCanvasElement; (tag:'table'): HTMLTableElement; (tag:string): HTMLElement; }构造函数:构造函数的实现通常用class写法来表示,构造函数的类型可以在函数前面加new关键字并返回一个class实例类型class Animal { numLegs:number = 4; } type AnimalConstructor = new () => Animal; function create(c:AnimalConstructor):Animal { return new c(); } const a = create(Animal);
10.对象类型
除了原始类型,对象是 JavaScript 最基本的数据结构。TypeScript 对于对象类型有很多规则。
对象类型的最简单声明方法,就是使用大括号表示对象,在大括号内部声明每个属性和方法的类型。
const obj: { x: number; y: number; } = { x: 1, y: 1 };
对象属性
- 属性操作
- 一旦声明了类型,对象赋值时,就不能缺少指定的属性,也不能有多余的属性。
type MyObj = { x: number; y: number; }; const o1: MyObj = { x: 1 }; // 报错 const o2: MyObj = { x: 1, y: 1, z: 1 }; // 报错 - 读写不存在的属性也会报错。
- 不能删除属性,修改可以
- 一旦声明了类型,对象赋值时,就不能缺少指定的属性,也不能有多余的属性。
- 可选属性
- 如果某个属性是可选的(即可以忽略),需要在属性名后面加一个问号
- 可选属性等同于允许赋值为
undefined,下面两种写法是等效的
-- 可选属性需要判断undefined才能使用type User = { firstName: string; lastName?: string; }; // 等同于 type User = { firstName: string; lastName: string | undefined; };// 写法一 let lastName = user.lastName === undefined ? "Bar" : user.lastName; // 写法二 let firstName = user.firstName ?? "Foo"; - 只读属性
- 不能修改,通过readonly关键字修饰
interface MyInterface { readonly prop: number; } 属性名的索引类型: 如果对象的属性非常多,一个个声明类型就很麻烦type MyObj = { [property: string]: string; }; const obj: MyObj = { foo: "a", bar: "b", baz: "c", };- 属性名只支持string,number,symbol
type T1 = { [property: number]: string; }; type T2 = { [property: symbol]: string; };- 可以同时多种属性索引,但是不能有冲突,number必须服从string索引
type MyType = { [x: number]: boolean; // 报错 [x: string]: string; }; type MyType2 = { foo: {a:string; b:number}; // 不报错 [x: string]: {a:string}; };- 属性名与属性索引也不能冲突
type MyType = { foo: boolean; // 报错 [x: string]: string; };
解构赋值写法
let { x: foo, y: bar }: { x: string; y: number } = obj;
结构类型原则
只要对象 B 满足 对象 A 的结构特征,TypeScript 就认为对象 B 兼容对象 A 的类型,这称为“结构类型”原则(structual typing)。
只要可以使用A的地方,都可以使用B
const B = { x: 1, y: 1, };
const A: { x: number } = B; // 正确
严格字面量检查
如果对象使用字面量表示,会触发 TypeScript 的严格字面量检查(strict object literal checking)。如果字面量的结构跟类型定义的不一样(比如多出了未定义的属性),就会报错。
const point: { x: number; y: number; } = {
x: 1, y: 1, z: 1,
// 报错
};
可以通过变量赋值或者类型断言规避字面量检查
type Mytype = { x: number; y: number; }
let point: Mytype = {
x: 1, y: 1, z: 1,
} as Mytype;
const myPoint2 = {
x: 1, y: 1, z: 1,
}
point = myPoint2;
或者通过suppressExcessPropertyErrors关闭字面量检查
最小属性原则
如果对象的所有属性都是可选的,那么在赋值的时候至少要有一个属性有值
type Options = { a?: number; b?: number; c?: number; };
const obj: Options = { d: 123, // 报错 };
空对象
{}可以作为值或者类型,但是该类型的对象不能操作除了原型链上的属性
const obj = {}
// 等同于 const obj:{} = {}
obj.prop = 123; // 报错
11.class类
- 属性的类型
- class的属性可以在顶部声明或者在构造函数里面声明
class Point { x: number; y: number; constructor(x: number, y: number) { this.x = x; this.y = y; } } // 构造函数里面声明时,修饰符不能省略 class Point { constructor(public x: number, public y: number) {} }- 属性如果有初始值则会自动推断类型
strictPropertyInitialization打开时需要手动设置初始值, 可以通过类型断言来规避报错
class Point { x = 1; y!: number; }- 只读属性只能在构造方法里面修改
- 方法的类型
- 类的方法就是普通函数,类型声明方式与函数一致。
class Point { log(x: string): void { console.log(string) } }- 构造方法和方法都支持函数重载,只是构造函数不能设置返回类型
class Point { constructor(x: number, y: string); constructor(s: string); constructor(xs: number | string, y?: string) { // ... } } - 静态成员
- 类的内部可以使用
static关键字,定义静态成员 - 静态成员是只能通过类本身使用的成员,不能通过实例对象使用
class MyClass { static x = 0; static printX() { console.log(MyClass.x); } } MyClass.x; // 0 MyClass.printX(); // 0 - 类的内部可以使用
- 存取器方法
- 存取器(accessor)是特殊的类方法,包括取值器(getter)和存值器(setter)两种方法
set方法的参数类型,必须兼容get方法的返回值类型,否则报错。set和get方法可访问性必须一致,都为公有或者私有
class C { _name = ''; get name() { return this._name; } set name(value) { this._name = value; } } - 类的 interface 接口
- class使用
implements关键字 来实现interface、type、class定义的类型
interface Country { name: string; capital: string; } // 或者 type Country = { name: string; capital: string; }; class MyCountry implements Country { name = ""; capital = ""; } class Car { id: number = 1; move(): void {} } class MyCar implements Car { id = 2; // 不可省略 move(): void {} // 不可省略 }- 实现多个接口
class Car implements MotorVehicle, Flyable, Swimmable { // ... }- 不同接口不能有互相冲突的属性
- class使用
- class类型
实例类型: 类名代表该类的实例类型,而不是 class 的自身类型类的自身类型:类的自身类型就是一个构造函数, 通过typeof获取
class Color { name: string; constructor(name: string) { this.name = name; } } const green: Color = new Color("green"); function createColor(ColorClass: typeof Color, x: string): Color { return new ColorClass(x); } const red: Color = createColor(Color, 'red')- 也遵循结构类型原则: 确定两个类的关系时,
只检查实例成员,不j检查静态成员和构造函数
- 类的继承
- 可以使用 extends 关键字继承另一个类(这里又称“基类”)的所有属性和方法。
class A { greet() { console.log("Hello, world!"); } } class B extends A {} const b = new B(); b.greet(); // "Hello, world!"- 子类也可以
重写继承过来的方法和属性- 子类的同名方法不能与基类的类型定义相冲突。
- 重写后可以通过
super关键字调用基类的方法和属性 - 子类可以改变继承下来的可访问性,改变
protected为其它修饰符,但是不能改变为private
- 可访问性修饰符
- 类的内部成员(属性,方法,构造方法,静态成员)的外部可访问性,由三个可访问性修饰符(access modifiers)控制:
public、private和protected。 public修饰符表示这是公开成员,外部可以自由访问private修饰符表示私有成员,只能用在当前类的内部,类的实例和子类都不能使用该成员。- 子类不能定义父类私有成员的同名成员
- ES6 引入了自己的私有成员写法
#propName。因此建议不使用private
protected修饰符表示该成员是保护成员,只能在类的内部使用该成员,实例无法使用该成员,但是子类内部可以使用。- 子类不仅可以拿到父类的保护成员,还可以定义同名成员。
class A { private x: number = 0; } const a = new A(); a.x; // 报错 class B extends A { showX() { console.log(this.x); // 报错 } } - 类的内部成员(属性,方法,构造方法,静态成员)的外部可访问性,由三个可访问性修饰符(access modifiers)控制:
- 抽象类、抽象成员
- TypeScript 允许在类的定义前面,加上关键字
abstract,表示该类不能被实例化,只能当作基类使用。这种类就叫做“抽象类”(abastract class)。
abstract class A { id = 1; } const a = new A(); // 报错- 抽象类只能当作基类使用,用来在它的基础上定义子类。
- 抽象类可以继承其他抽象类。
抽象成员:抽象类的内部未实现的属性和方法。叫做“抽象成员”- 属性名和方法名有
abstract关键字,表示该方法需要子类实现。 - 如果子类没有实现抽象成员,就会报错。
- 抽象成员只能存在于抽象类,不能存在于普通类
- 抽象成员不能有具体实现的代码
- 抽象成员前也不能有
private修饰符
- 属性名和方法名有
- TypeScript 允许在类的定义前面,加上关键字
- this问题
- 函数增加一个名为
this的参数,放在参数列表的第一位,用来描述函数内部的this关键字的类型
class A { name = "A"; getName(this: A) { return this.name; } } const a = new A(); const b = a.getName; b(); // 报错- 在类的内部,
this本身也可以当作类型使用,表示当前类的实例对象。
class Box { contents: string = ""; set(value: string): this { this.contents = value; return this; } } - 函数增加一个名为
四.泛型
泛型就是将某些类型作为参数,在使用的时候传入,而不是一开始就定义好
参数要放在一对尖括号(<>)里面
例如我们常用的数组泛型
let stringArr: Array<string>
let numberArr: Array<number>
函数泛型的写法
// 写法一
function id<T>(arg: T): T { return arg; }
// 写法二
let myId: <T>(arg: T) => T = id;
// 写法三
let myId2: { <T>(arg: T): T } = id;
interface泛型的写法
interface Box<Type> {
contents: Type;
}
let box: Box<string>;
类的泛型写法
class Pair<K, V> {
key: K;
value: V;
constructor(key: K, value: V) {
this.key = key;
this.value = value
}
}
const a = new Pair<string, string>()
- 继承泛型类时,必须给出类型
class A<T> { value: T; }
class B extends A<any> {}
- 静态属性不能使用泛型类
类型别名的泛型写法
type Container<T> = { value: T };
const a: Container<number> = { value: 0 };
const b: Container<string> = { value: "b" };
递归嵌套
type Tree<T> = {
value: T;
left: Tree<T> | null;
right: Tree<T> | null;
};
默认类型
使用时,如果泛型没有给出类型参数的值,就会使用默认值
function getFirst<T = string>(arr: T[]): T {
return arr[0];
}
类型参数的约束条件
使用<TypeParameter extends ConstraintType>对泛型进行约束,不满足时会报错
function comp<T extends { length: number }>(a: T, b: T) {
if (a.length >= b.length) { return a; }
return b;
}
泛型参数可以使用其他泛型作为约束
<T, U extends T>
五.类型运算
1.交叉
交叉类型(intersection types)指的多个类型组成的一个新类型,使用符号&表示。
- 交叉类型的主要用途是表示对象的合成
let obj: { foo: string } & { bar: string };
obj = {
foo: "hello",
bar: "world",
};
- 当交叉类型由互斥的基本类型组成时,ts会认为实际类型是never
let x: number & string; // never
- 当有关联的两个类组成交叉类型时,返回子类型
type A = string & 'h' //'h'
- 任何值与known相交都返回其本身(known可以看作所有类型的父类)
type T = unknown & null; // null
T & {},除了undefined和null,其它类型都属于{}子类型,所以相交也返回子类型,而undefined和null则返回never
2.联合
联合类型(union types)指的是多个类型组成的一个新类型,使用符号|表示。
联合类型A|B表示,任何一个类型只要属于A或B,就属于联合类型A|B。
let x: string | number;
x = 123; // 正确
x = "abc"; // 正确
前面提到,打开编译选项strictNullChecks后,其他类型的变量不能赋值为undefined或null。这时,如果某个变量确实可能包含空值,就可以采用联合类型的写法。
let name: string | null;
name = "John";
name = null;
3.类型缩小
如果一个变量可能有多个类型,那么在使用它时需要进行类型缩小。
通常需要结合if语句或者switch语句。当然最基本的赋值缩小不需要结合判断语句
赋值缩小
let x: string | number
x = 'hello'
x.toFixed(111) // 报错
x = 123;
x.toFixed(111) // 正确
- 通过
typeof缩小
function printId(id: number | string) {
if (typeof id === "string") {
console.log(id.toUpperCase());
} else {
console.log(id);
}
}
- 通过
instance缩小
function f(x: Date | RegExp) {
if (x instanceof Date) { x; // Date }
if (x instanceof RegExp) { x; // RegExp }
}
- 通过
in缩小
interface A { x: number; }
interface B { y: string; }
function f(x: A | B) {
if ("x" in x) { x; // A }
else { x; // B }
}
- 通过
特征属性缩小
interface UploadEvent {
type: "upload";
filename: string;
contents: string;
}
interface DownloadEvent {
type: "download";
filename: string;
}
type AppEvent = UploadEvent | DownloadEvent;
function handleEvent(e: AppEvent) {
switch (e.type) {
case "download":
e; // Type is DownloadEvent
break;
case "upload":
e; // Type is UploadEvent
break;
}
}
- 通过
is进行类型保护
function isInputElement(el: HTMLElement): el is HTMLInputElement {
return "value" in el;
}
function getElementContent(el: HTMLElement) {
if (isInputElement(el)) {
el; // Type is HTMLInputElement
return el.value;
}
el; // Type is HTMLElement
return el.textContent;
}
4.类型兼容
结构类型原则
ts 的一个规则是,凡是可以使用对象A的地方,都可以使用对象B,那么类型A就称为类型B的子类型(subtype)。但是子类型不能使用父类型。
type RootType = {
a: string
}
type ChildType {
a: string
b: string
}
const child: ChildType = {
a: 'a',
b: 'b'
}
const root: RootType = child;
const a: 'hello' = 'hello'
const b: string = a
- 所以对于
顶层类型any,我们可以理解为它是所有类型的子类型,因为any类型的变量可以赋予其它变量。它同时也是所有类型的父类型,因为any类型的变量可以被赋予任意类型的值
let a: any
a = 123
let b: string
b = a
- 同理对于底层类型
never,是所有类型的子类型
let a: never
a = 123 // 报错
let b: string
b = a // 不报错
- 对于
unknown,是其它所有类型的父类型,只是any的子类型
子类型和父类型的概念可以方便我们理解常见的类型运算,比如赋值, &, 与 extends
type T30<T> = unknown extends T ? true : false;
type T31 = T30<string> // false
type T32<T> = T extends unknown ? true : false;
type T33 = T32<string> // true
对于有关联的联合类型,我们也可以引入父类子类概念
例如:1|2 可以看成 1|2|3的子类
type T0<T> = [T] extends [1|2] ? true : false
type T1 = T0<1> // true
type T2 = T0<1|2|3> // false
运算律
- 改变成员类型的顺序不影响联合类型的结果类型。
type T0 = string | number;
type T1 = number | string;
优先级
&的优先级高于|A | B & C 等于 A | (B & C)
分配律
-
(A | B) & (C | D) ≡ A & C | A & D | B & C | B & DT = (string | 0) & (number | "a"); T = (string & number) | (string & "a") | (0 & number) | (0 & "a"); T = never | "a" | 0 | never; T = "a" | 0;
六.类型运算符
1.typeof运算符
- TypeScript 将typeof运算符移植到了类型运算,它的操作数依然是一个值,但是返回的不是字符串,而是该值的 TypeScript 类型。
const a = { x: 0 };
type T0 = typeof a; // { x: number }
type T1 = typeof a.x; // number
- typeof 的参数只能是标识符,不能是需要运算的表达式
type T = typeof Date(); // 报错
- typeof命令的参数不能是类型。
type Age = number;
type MyAge = typeof Age; // 报错
2.keyof运算符
- 返回对象所有键名组成的联合类型
type MyObj = {
foo: number;
bar: string;
};
type Keys = keyof MyObj; // 'foo'|'bar'
- 对于any
// string | number | symbol
type KeyT = keyof any;
- 对象有属性索引,则返回索引类型
interface T {
[prop: number]: number;
}
// number
type KeyT = keyof T;
- 对于数组或元祖
type Result = keyof ["a", "b", "c"];
// 返回 number | "0" | "1" | "2"
// | "length" | "pop" | "push" | ···
- 对于联合类型,keyof 返回成员共有的键名
type A = { a: string; z: boolean };
type B = { b: string; z: boolean };
// 返回 'z'
type KeyT = keyof (A | B);
- 对于交叉类型,keyof 返回所有键名
keyof (A & B) ≡ keyof A | keyof B
3.readonly
readonly设为只读, -readonly表示去除只读
type Mutable<Obj> = {
-readonly [Prop in keyof Obj]: Obj[Prop];
};
// 用法
type MyObj = {
readonly foo: number;
};
// 等于 { foo: number; }
type NewObj = Mutable<MyObj>;
4.?
?变为可选,-? 去除可选
type Concrete<Obj> = {
[Prop in keyof Obj]-?: Obj[Prop];
};
// 用法
type MyObj = {
foo?: number;
};
// 等于 { foo: number; }
type NewObj = Concrete<MyObj>;
5.in
遍历联合类型中的每一个成员
type U = "a" | "b" | "c";
type Foo = {
[Prop in U]: number;
};
// 等同于
type Foo = {
a: number;
b: number;
c: number;
};
6.方括号运算符
取出对象属性的类型
type Person = { age: number; name: string; alive: boolean; };
// Age 的类型是 number
type Age = Person["age"];
- 如果该属性不存在,则报错
- 方括号的参数如果是联合类型,那么返回的也是联合类型。
- 也可以取出索引属性的类型
type Obj = { [key: string]: number; };
// number
type T = Obj[string];
7.extends
条件运算符,语法为 T extends U ? X : Y。
类型T是否可以赋值给类型U,即T是否为U的子类型,这里的T和U可以是任意类型。
- 对于联合类型
(A|B) extends U ? X : Y
// 等同于
(A extends U ? X : Y) |
(B extends U ? X : Y)
- 如果只是不希望展开,可以将两侧类型都放到方括号里
// 示例一
type ToArray<Type> = Type extends any ? Type[] : never;
// string[]|number[]
type T = ToArray<string | number>;
// 示例二
type ToArray<Type> = [Type] extends [any] ? Type[] : never;
// (string | number)[]
type T = ToArray<string | number>;
8.infer
它通常跟条件运算符一起使用,用在extends关键字后面的父类型之中。用来提取左边子类型的一部分而不是全部
// 提取数组类型
type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;
type Str = Flatten<string[]>; // string
// 提取函数参数及返回值
type ReturnPromise<T> = T extends (...args: infer A) => infer R
? (...args: A) => Promise<R>
: T;
// 提取对象属性类型
type MyType<T> = T extends {
a: infer M;
b: infer N;
}
? [M, N]
: never;
// 提取字符串
type Str = "foo-bar";
type Bar = Str extends `foo-${infer rest}` ? rest : never; // 'bar'
9.is
函数返回布尔值的时候,可以使用is运算符,限定返回值与参数之间的关系。写法采用parameterName is Type的形式
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
- is可以用于类型保护
function isCat(a: any): a is Cat {
return a.name === "kitty";
}
let x: Cat | Dog;
if (isCat(x)) {
x.meow(); // 正确,因为 x 肯定是 Cat 类型
}
10.模板字符串
TypeScript 允许使用模板字符串,构建类型。
模板字符串的最大特点,就是内部可以引用其他类型。
type World = "world"; // "hello world"
type Greeting = `hello ${World}`;
- 模板字符串可以引用的类型一共 6 种,分别是 string、number、bigint、boolean、null、undefined
七.断言
expr as T
类型断言要求实际的类型与断言的类型兼容,实际类型可以断言为一个更加宽泛的类型(父类型),也可以断言为一个更加精确的类型(子类型),但不能断言为一个完全无关的类型。
expr as unknown as T
如果真的要断言成一个完全无关的类型,也是可以做到的。那就是连续进行两次类型断言,先断言成 unknown 类型或 any 类型,然后再断言为目标类型
as const断言- as const会将字面量的类型断言为不可变类型,缩小成 TypeScript 允许的最小类型。
- 只能用于字面量,不能用于变量
// a1 的类型推断为 number[] const a1 = [1, 2, 3]; // a2 的类型推断为 readonly [1, 2, 3] const a2 = [1, 2, 3] as const;- 非空断言
- 对于那些可能为空的变量(即可能等于undefined或null),TypeScript 提供了非空断言,保证这些变量不会为空,写法是在
变量名后面加上感叹号!。 - 非空断言只有在打开编译选项
strictNullChecks时才有意义
- 对于那些可能为空的变量(即可能等于undefined或null),TypeScript 提供了非空断言,保证这些变量不会为空,写法是在
- 断言函数
- 语法:用
asserts对函数参数进行类型判断 - asserts语句等同于void类型,所以如果返回除了undefined和null以外的值,都会报错
function isString(value:unknown):asserts value is string { if (typeof value !== 'number') throw new Error('Not a number'); } function assert(x:unknown):asserts x { if (!x) { throw new Error(`${x} should be a truthy value.`); } }断言函数与类型保护函数(type guard)是两种不同的函数。它们的区别是,断言函数不返回值,而类型保护函数总是返回一个布尔值。
function isString( value:unknown ):value is string { return typeof value === 'string'; } - 语法:用
八.类型工具
ts内置了很多种类型工具,能够直接使用
1.Awaited<T>
- 取出promise的返回值
- 如果不是promise,则返回原值
// number | boolean
type C = Awaited<boolean | Promise<number>>;
底层实现
type Awaited<T> =
T extends null | undefined ? T : // special case for `null | undefined`
// when not in `--strictNullChecks` mode
T extends object & { then(onfulfilled: infer F, ...args: infer _): any } ?
// `await` only unwraps object types with a callable `then`.
// Non-object types are not unwrapped
F extends ((value: infer V, ...args: infer _) => any) ?
// if the argument to `then` is callable, extracts the first argument
Awaited<V> : // recursively unwrap the value
never : // the argument to `then` was not callable
T; // non-object or non-thenable
2.ReturnType<Type>
提取函数类型的返回值类型
type T1 = ReturnType<() => string>; // string
实现
type ReturnType<T extends (...args: any) => any> = T extends (
...args: any
) => infer R
? R
: any;
3.Parameters<Type>
提取函数参数类型, 返回一个元祖类型
type T2 = Parameters<(s: string) => void>; // [s:string]
实现
type Parameters<T extends (...args: any) => any> = T extends (
...args: infer P
) => any
? P
: never;
4.ThisParameterType<Type>
提取函数类型中this参数的类型
function toHex(this: Number) {
return this.toString(16);
}
type T = ThisParameterType<typeof toHex>; // number
实现
type ThisParameterType<T> =
T extends (
this: infer U,
...args: never
) => any ? U : unknown;
5.InstanceType<Type>
提取构造函数的返回值的类型(即实例类型)
type T = InstanceType<new () => object>; // object
class C {
x = 0;
y = 0;
}
type T = InstanceType<typeof C>; // C
实现
type InstanceType<T extends abstract new (...args: any) => any> =
T extends abstract new (...args: any) => infer R ? R : any;
6.ConstructorParameters<Type>
提取构造函数的参数,返回一个元祖
type T2 = ConstructorParameters<new (x?: string) => object>;
// [x?: string | undefined]
any类型和never类型是两个特殊值,分别返回unknown[]和never
实现
type ConstructorParameters<T extends abstract new (...args: any) => any> =
T extends abstract new (...args: infer P) => any ? P : never;
7.ThisType<Type>
不返回类型,只用来跟其他类型组成交叉类型,用来提示 TypeScript 其他类型里面的this的类型。注意,使用这个类型工具时,必须打开noImplicitThis设置
interface HelperThisValue {
logError: (error: string) => void;
}
let helperFunctions: { [name: string]: Function } & ThisType<HelperThisValue> =
{
hello: function () {
this.logError("Error: Something wrong!"); // 正确
this.update(); // 报错
},
};
8.OmitThisParameter<Type>
从函数类型中移除 this 参数
function toHex(this: Number) {
return this.toString(16);
}
type T = OmitThisParameter<typeof toHex>; // () => string
实现
type OmitThisParameter<T> = unknown extends ThisParameterType<T>
? T
: T extends (...args: infer A) => infer R
? (...args: A) => R
: T;
9.Exclude<UnionType, ExcludedMembers>
用来从联合类型UnionType里面,删除某些类型ExcludedMembers,组成一个新的类型返回
type T = Exclude<number | boolean, boolean>; // number
type T2 = Exclude<number | boolean, boolean | number>; // never
实现
type Exclude<T, U> = T extends U ? never : T;
10.Extract<Type, Union>
用来从联合类型UnionType之中,提取指定类型Union,组成一个新类型返回。它与Exclude<T, U>正好相反
type T6 = Extract<200 | 400, 200 | 201>; // 200
实现
type Extract<T, U> = T extends U ? T : never;
11.Pick<Type, Keys>
返回一个新的对象类型,第一个参数Type是一个对象类型,第二个参数Keys是Type里面被选定的键名
interface A {
x: number;
y: number;
}
type T1 = Pick<A, "x">; // { x: number }
实现
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
12.Omit<Type, Keys>
用来从对象类型Type中,删除指定的属性Keys,组成一个新的对象类型返回。
interface A {
x: number;
y: number;
}
type T1 = Omit<A, "x">; // { y: number }
实现
type Omit<T, U> = Pick<T, Exclude<keyof T, U>>
13.NonNullable<Type>
用来从联合类型Type删除null类型和undefined类型,组成一个新类型返回,也就是返回Type的非空类型版本
type NonNullable<T> = T & {};
type T = NonNullable<string | null> //string
T & {}等同于求T & Object的交叉类型。由于 TypeScript 的非空值都属于Object的子类型,所以会返回自身;而null和undefined不属于Object,会返回never类型。
14.Readonly<Type>
返回一个新类型,将参数类型Type的所有属性变为只读属性 type Readonly = { readonly [P in keyof T]: T[P]; };
15.ReadonlyArray<Type>
返回一个只读数组
interface ReadonlyArray<T> {
readonly length: number;
readonly [n: number]: T;
// ...
}
16.Partial<Type>
返回一个新类型,将参数类型Type的所有属性变为可选属性
type Partial<T> = {
[P in keyof T]?: T[P];
};
返回一个新类型,将参数类型Type的所有属性变为可选属性
17.Required<Type>
返回一个新类型,将参数类型Type的所有属性变为必选属性 type Required = { [P in keyof T]-?: T[P]; };
18.Record<Keys, Type>
返回一个对象类型,参数Keys用作键名,参数Type用作键值类型
// { a: number, b: number }
type T = Record<"a" | "b", number>;
实现
type Record<K extends string | number | symbol, T> = { [P in K]: T };
19.Uncapitalize<StringType>
将字符串的第一个字符转为小写。
type A = "HELLO";
// "hELLO"
type B = Uncapitalize<A>;
20.Capitalize<StringType>
将字符串的第一个字符转为大写
21.Uppercase<StringType>
将字符串类型的每个字符转为大写。
22.Lowercase<StringType>
将字符串的每个字符转为小写