TypeScript
类型总览
JavaScript 的类型
string
number
boolean
undefined
null
bigint
symbol
object
引用类型 (前面 7 个都是原始类型)
其中,object
类型可以包含 Array
、Function
、Date
、Error
等。
TypeScript 的类型
- JavaScript 的全部类型
any
unknown
never
tuple
enum
两个用于自定义类型的关键字:
type
interface
注
在 JavaScript 中的这些内置构造函数:Number
、String
、Boolean
,⽤于创建对应的包装对象, 在⽇常开发时很少使⽤,在 TypeScript 中也是同理,所以在 TypeScript 中进⾏类型声明时,通常都是⽤⼩写的 number
、string
、boolean
注
- 字面量类型:字面量类型是和原始类型以及对象类型对应的
- 字面量类型和联合类型天生一对
type Status = "success" | "failure";
type Code = 200 | 404 | 502;
原始类型和包装对象
- 原始类型:如 number 、 string 、 boolean ,在 JavaScript 中是简单数据类型,它们在内存中占⽤空间少,处理速度快。
- 包装对象:如 Number 对象、 String 对象、 Boolean 对象,是复杂类型,在内存中占⽤更多空间,在⽇常开发时很少由开发⼈员⾃⼰创建包装对象。
⾃动装箱:JavaScript 在必要时会⾃动将原始类型包装成对象,以便调⽤⽅法或访问属性
// 原始类型字符串
let str = "hello";
// 当访问 str.length时,JavaScript引擎做了以下⼯作:
let size = (function () {
// 1. ⾃动装箱:创建⼀个临时的String对象包装原始字符串
let tempStringObject = new String(str);
// 2. 访问String对象的length属性
let lengthValue = tempStringObject.length;
// 3. 销毁临时对象,返回⻓度值
// JavaScript引擎⾃动处理对象销毁,开发者⽆感知
return lengthValue;
})();
console.log(size); // 输出: 5
常用类型介绍
any
any 的含义是:任意类型,⼀旦将变量类型限制为 any ,那就意味着放弃了对该变量的类型检查。
unknown
unknown 的含义是:未知类型,适⽤于:起初不确定数据的具体类型,要后期才能确定
unknown 可以理解为⼀个类型安全的 any
never
never 的含义是:任何值都不是,即:不能有值,例如 undefined
、 null
、 ''
、 0
都不⾏!
- ⼏乎不⽤ never 去直接限制变量,因为没有意义
- never ⼀般是 TypeScript 主动推断出来的,例如:
// 指定 a 的类型为 string
let a: string;
// 给a设置⼀个值
a = "hello";
if (typeof a === "string") {
console.log(a.toUpperCase());
} else {
console.log(a); // TypeScript 会推断出此处的 a 是 never,因为没有任何⼀个值符合此处的逻辑
}
void
void 的含义是空,即:函数不返回任何值,调⽤者也不应依赖其返回值进⾏任何操作!
function logMessage(msg: string): void {
console.log(msg);
}
logMessage("你好");
注
编码者没有编写 return 指定函数返回值,所以 logMessage 函数是没有显式返回值的,但会有⼀个隐式返回值 ,是 undefined
,虽然函数返回类型为 void
,但也是可以接受 undefined
的,简单记: undefined
是 void
可以接受的⼀种“空”。
object
object(⼩写)的含义是:所有⾮原始类型,可存储:对象、函数、数组等,由于限制的范围⽐较宽泛,在实际开发中使⽤的相对较少。
注
Object(⼤写)
- 官⽅描述:所有可以调⽤ Object ⽅法的类型。
- 简单记忆:除了 undefined 和 null 的任何值。
声明对象类型
let person1: { name: string; age?: number };
// 索引签名
// 允许定义对象可以具有任意数量的属性,这些属性的键和类型是可变的
let person: {
name: string;
age?: number;
[key: string]: any; // 索引签名,完全可以不⽤ key 这个单词,换成其他的也可以
};
声明函数类型
let count: (a: number, b: number) => number;
count = function (x, y) {
return x + y;
};
声明数组类型
let arr1: string[];
let arr2: Array<string>;
arr1 = ["a", "b", "c"];
arr2 = ["hello", "world"];
// 接口也可以用来描述数组
interface NumberArray {
[index: number]: number; // 只要索引的类型是数字时,那么值的类型必须是数字
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
tuple
元组 (Tuple) 是⼀种特殊的数组类型,可以存储固定数量的元素,并且每个元素的类型是已知的且可以不同。元组⽤于精确描述⼀组值的类型, ? 表示可选元素。
// 第⼀个元素必须是 string 类型,第⼆个元素必须是 number 类型。
let arr1: [string, number];
arr1 = ["hello", 123];
enum
枚举 (Enum) 是⼀种⾮常有⽤的数据类型,它允许⼀组命名的常量,这些常量可以⽤于代码中,以提⾼代码的可读性。
数字枚举
数字枚举⼀种最常⻅的枚举类型,其成员的值会⾃动递增,且数字枚举还具备反向映射的特点,在下⾯代码的打印中,不难发现:可以通过值来获取对应的枚举成员名称 。
enum Direction {
Up,
Down,
Left,
Right,
}
console.log(Direction); // 打印 Direction 会看到如下内容
// {
// 0:'Up',
// 1:'Down',
// 2:'Left',
// 3:'Right',
// Up:0,
// Down:1,
// Left:2,
// Right:3
// }
// 也可以指定枚举成员的初始值,其后的成员值会⾃动递增。
enum Direction {
Up = 6,
Down,
Left,
Right,
}
console.log(Direction.Up); // 输出: 6
console.log(Direction.Down); // 输出: 7
字符串枚举
enum Direction {
Up = "up",
Down = "down",
Left = "left",
Right = "right",
}
let dir: Direction = Direction.Up;
console.log(dir); // 输出: "up"
常量枚举
type
type 可以为任意类型创建别名,让代码更简洁、可读性更强,同时能更⽅便地进⾏类型复⽤和扩展。
基本用法
type num = number;
let price: num price = 100
联合类型 (或)
type num = number | string;
let price: num;
price = 100;
price = "100";
交叉类型 (且)
交叉类型(Intersection Types)允许将多个类型合并为⼀个类型。合并后的类型将拥有所有被合并类型的成员。交叉类型通常⽤于对象类型。
//⾯积
type Area = {
height: number; // ⾼
width: number; // 宽
};
//地址
type Address = {
num: number; // 楼号
cell: number; // 单元号
room: string; // 房间号
};
// 定义类型 House,且 House 是 Area 和 Address 组成的交叉类型
type House = Area & Address;
const house: House = { height: 180, width: 75, num: 6, cell: 3, room: "702" };
接口 interface
interface
是⼀种定义结构的⽅式,主要作⽤是为:类、对象、函数等规定⼀种契约,这样可以确保代码的⼀致性和类型安全,但要注意 interface
只能定义格式,不能包含任何实现!
定义类结构
// PersonInterface 接⼝,⽤于限制 Person 类的格式
interface PersonInterface {
name: string;
age: number;
speak(n: number): void;
}
// 定义⼀个类 Person,实现 PersonInterface 接⼝
class Person implements PersonInterface {
constructor(public name: string, public age: number) {}
// 实现接⼝中的 speak ⽅法
speak(n: number): void {
for (let i = 0; i < n; i++) {
// 打印出包含名字和年龄的问候语句
console.log(`你好,我叫${this.name},我的年龄是${this.age}`);
}
}
}
// 创建⼀个 Person 类的实例 p1,传⼊名字 'tom' 和年龄 18 const p1 = new Person('tom', 18);
p1.speak(3);
定义对象结构
interface UserInterface {
name: string;
readonly gender: string; // 只读属性
age?: number; // 可选属性
run: (n: number) => void;
}
const user: UserInterface = {
name: "张三",
gender: "男",
age: 18,
run(n) {
console.log(`奔跑了${n}⽶`);
},
};
定义函数结构
interface CountInterface {
(a: number, b: number): number;
}
const count: CountInterface = (x, y) => {
return x + y;
};
接口继承
interface PersonInterface {
name: string; // 姓名
age: number; // 年龄
}
interface StudentInterface extends PersonInterface {
grade: string; // 年级
}
const stu: StudentInterface = { name: "张三", age: 25, grade: "⾼三" };
注
接口重复定义时,会自动合并
declare
declare 用于声明全局变量、函数、类、命名空间等,用于告诉 TypeScript 这些变量、函数、类、命名空间等在编译时存在,但不会在编译时生成对应的代码。
重点:declare 更像声明存在,一般用在外部库或全局环境。
interface | type
更像约束类型,用于项目内部 类型检查和赋值实现。
declare const name: string;
declare function greet(name: string): void;
// 类比 interface
interface Greet {
(name: string): void;
}
const greet: Greet = (name) => {
console.log(`Hello, ${name}`);
};
// 类比 type
type Greet = (name: string) => void;
const greet: Greet = (name) => {
console.log(`Hello, ${name}`);
};
类知识 Class
// 定义类
class Person {
// 属性声明
name: string;
age: number;
// 构造器
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
// ⽅法
speak() {
console.log(`我叫:${this.name},今年${this.age}岁`);
}
}
// Person 实例
const p1 = new Person("周杰伦", 38);
继承
class Student extends Person {
grade: string;
// 构造器
constructor(name: string, age: number, grade: string) {
super(name, age);
this.grade = grade;
}
// 备注本例中若 Student 类不需要额外的属性,Student 的构造器可以省略
// 重写从⽗类继承的⽅法
override speak() {
console.log(`我是学⽣,我叫:${this.name},今年${this.age}岁,在读${this.grade} 年级`);
}
// ⼦类⾃⼰的⽅法
study() {
console.log(`${this.name}正在努⼒学习中......`);
}
}
属性修饰符
修饰符 | 描述 |
---|---|
public | 公共的,可以在类内部、子类和类外部访问(默认) |
protected | 受保护的,可以在类内部和子类中访问,类的实例无法访问 |
private | 私有的,只能在类内部访问,子类和类的实例均无法访问 |
readonly | 只读的,属性无法修改 |
static | 静态属性修饰符,只能通过类名访问,不能通过实例访问 |
属性简写
在 构造函数参数前加访问修饰符,TypeScript 会自动:
- 声明同名属性。
- 初始化赋值。
// 完整写法
class Person {
public name: string;
public age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
// 简写
class Person {
constructor(public name: string, public age: number) {}
}
抽象类
抽象类(abstract class) 是一种不能被实例化的类,它主要用作其他类的基类(父类),目的是提供一个模板或规范,由子类来实现具体逻辑。
抽象类就像是“半成品类”,它定义了类的结构(属性、方法),但是不实现所有细节。你必须通过继承来创建“完整的产品”。
// 抽象类
abstract class Package {
constructor(public weight: number) {}
// 抽象⽅法:⽤来计算运费,不同类型包裹有不同的计算⽅式
// 抽象方法只能出现在抽象类里,不能有方法体(不能写大括号和实现代码)。
abstract calculate(): number;
// 通⽤⽅法:打印包裹详情
printPackage() {
console.log(`包裹重量为: ${this.weight}kg,运费为: ${this.calculate()}元`);
}
}
// 子类
class StandardPackage extends Package {
constructor(weight: number, public unitPrice: number) {
// 调用父类的构造器
super(weight);
}
// 实现抽象⽅法:计算运费
calculate(): number {
return this.weight * this.unitPrice;
}
}
// 创建标准包裹实例
const s1 = new StandardPackage(10, 5);
s1.printPackage();
泛型
泛型允许我们在定义函数、类或接⼝时,使⽤类型参数来表示未指定的类型,这些参数在具体使⽤时,才被指定具体的类型,泛型能让同⼀段代码适⽤于多种类型,同时仍然保持类型的安全性。
泛型本质上就是类型世界中的参数
// 泛型函数:<T> 表示类型参数,T 是泛型参数,可以传入任何类型
function logData<T>(data: T): T {
console.log(data);
return data;
}
logData<number>(100);
logData<string>("hello");
概念区别
interface 和 type 的区别
区别点 | interface | type |
---|---|---|
定义方式 | 使用 interface 关键字 | 使用 type 关键字 |
声明合并 | 接口声明自动合并 | 重复声明会报错 |
继承 | 可以继承,使用 extends 关键字 | 使用 & 关键字交叉类型实现继承 |
定义基本类型 | 不可以 | 可以 type num = number |
更适合 | 描述对象或类的结构 | 定义复杂的类型、联合类型 |
interface 和抽象类的区别
- 相同点:都能定义⼀个类的格式(定义类应遵循的契约)
- 不相同:
- 接⼝:只能描述结构,不能有任何实现代码,⼀个类可以实现多个接⼝。
- 抽象类:既可以包含抽象⽅法,也可以包含具体⽅法, ⼀个类只能继承⼀个抽象类。
类型声明文件
类型声明⽂件是 TypeScript 中的⼀种特殊⽂件,通常以 .d.ts
作为扩展名。它的主要作⽤是为现有的 JavaScript 代码提供类型信息,使得 TypeScript 能够在使⽤这些 JavaScript 库或模块时进⾏类型检查和提示。
概括地说,类型声明文件就是一种不包括任何实际逻辑,仅仅包含类型信息,并且无需导入操作,就能够被 TypeScript 自动加载的文件。也就是说,如果定义了类型声明文件,即使你都不知道这个文件放在哪里了,其中的类型信息也能够被加载,然后成为你开发时的类型提示来源。
类型声明可以让一些老旧的 JavaScript 库在 TypeScript 中得到更好的类型支持。只需要提供一个类型声明包即可。
declare module "lodash" {
camelCase(string?: string): string;
capitalize(string?: string): string;
endsWith(string?: string): string;
// ...
}
首先是 declare module "lodash"
,可以称它为模块类型声明,它的作用其实就是告诉 TypeScript ,我们要为模块(module)lodash 进行类型声明(declare),在导入这个模块并访问属性时,你需要提示这个对象上具有 camelCase,capitalize 等方法。
常用的类型工具
内置工具类型速查
结构变换
Partial<T>
: 将 T 的所有属性设为可选。Required<T>
: 将 T 的所有可选属性改为必填。Readonly<T>
: 将 T 的所有属性设为只读。
映射类型
Record<K, T>
: 以联合类型 K 为键、T 为值构造对象类型。Pick<T, K>
: 从 T 中挑选属性 K 组成新类型。Omit<T, K>
: 从 T 中排除属性 K 组成新类型。
联合类型运算
Exclude<T, U>
: 从联合类型 T 中排除能赋给 U 的部分。Extract<T, U>
: 从联合类型 T 中提取能赋给 U 的部分。NonNullable<T>
: 从 T 中移除 null 和 undefined。
函数与类工具
Parameters<F>
: 函数参数元组类型。ReturnType<F>
: 函数返回值类型。ConstructorParameters<C>
: 构造函数参数元组类型。InstanceType<C>
: 构造函数实例类型。
this 相关 很少使用
ThisParameterType<F>
: 提取函数显式 this 参数类型。OmitThisParameter<F>
: 移除函数的显式 this 参数。ThisType<T>
: 在对象字面量中声明 this 的类型(配合 noImplicitThis)。Awaited<T>
: 递归拆箱 Promise 的最终值类型。
type User = { id: number; name: string; email?: string };
// 结构变换
type U1 = Partial<User>; // { id?: number; name?: string; email?: string }
type U2 = Required<User>; // { id: number; name: string; email: string }
const u3: Readonly<User> = { id: 1, name: "A", email: "[email protected]" };
// u3.name = '' // ❌ 只读
// 映射类型
type ScoreMap = Record<string, number>; // { [key: string]: number }
type UserPreview = Pick<User, "id" | "name">; // 只保留 id、name
type UserWithoutEmail = Omit<User, "email">; // 去掉 email
// 联合类型运算
type A = "a" | "b" | "c";
type B = Exclude<A, "a">; // "b" | "c" 排除
type C = Extract<A, "a" | "x">; // "a" 提取
type D = NonNullable<string | undefined | null>; // string
// 函数与类工具
function foo(a: number, b: string) {
return { a, b };
}
type P = Parameters<typeof foo>; // [number, string]
type R = ReturnType<typeof foo>; // { a: number; b: string }
class Person {
constructor(public name: string) {}
}
type CP = ConstructorParameters<typeof Person>; // [string]
type IP = InstanceType<typeof Person>; // Person 构造函数的实例类型 常用
// this 相关
function bar(this: { x: number }, y: number) {
return this.x + y;
}
type TP = ThisParameterType<typeof bar>; // { x: number }
type OTP = OmitThisParameter<typeof bar>; // (y: number) => number
type Ctx = { count: number };
let obj: ThisType<Ctx> & { inc(): void } = {
inc() {
this.count++;
},
};
// 分配时配合类型断言或泛型让 this 推断为 Ctx
// Promise 拆箱
type A1 = Awaited<Promise<number>>; // number
type A2 = Awaited<Promise<Promise<string>>>; // string
关键字与语法糖速查
keyof
: 取对象类型的所有键组成的联合类型。typeof
(类型查询): 从变量/值推断其类型用于类型位置。in
(映射类型中): 遍历联合类型生成键。readonly
: 标记属性/元组元素只读。as const
: 值层面的常量断言,得到最窄字面量类型且只读。satisfies
: 值需满足某接口但保持原字面量推断。extends
(两用): 泛型约束;条件类型分支。infer
: 在条件类型中占位、提取类型参数。is
(类型谓词): 声明函数返回值为类型守卫。- 模板字面量类型:
\
A−{B}`` 组合字符串字面量。
type User = { id: number; name: string; email?: string };
// keyof
type UserKeys = keyof User; // "id" | "name" | "email"
// typeof(类型位置)
const config = { env: "prod", retry: 3 } as const;
type Config = typeof config; // { readonly env: "prod"; readonly retry: 3 }
// in(映射类型)
type Flag<T extends string> = { [K in T]: boolean };
type F = Flag<"a" | "b">; // { a: boolean; b: boolean }
// readonly 与元组
type Point = readonly [number, number];
const p: Point = [1, 2];
// p[0] = 3 // ❌ 只读
// as const:窄化+只读
const routes = ["/", "/about", "/posts"] as const;
type Route = (typeof routes)[number]; // "/" | "/about" | "/posts" 联合类型
// [number] 是索引访问类型的一种写法,意思是访问数组或元组中所有数字索引对应的元素类型
// satisfies:校验而不扩大或缩窄既有推断
type HasId = { id: number };
const item = { id: 1, name: "x" } satisfies HasId; // ✅ 校验通过
// item 保持 { id: 1; name: "x" } 的最窄推断
// extends:泛型约束 + 条件类型
type IdOf<T extends { id: unknown }> = T["id"]; // 约束
type IsString<T> = T extends string ? true : false; // 条件类型
type User = { id: number; name: string };
type UserId = IdOf<User>; // 等价于 User["id"],也就是 number
// infer:提取函数参数/返回值/数组元素等
type FirstArg<F> = F extends (arg: infer A, ...rest: any[]) => any ? A : never;
type Elem<T> = T extends (infer U)[] ? U : never;
// is:类型守卫
function isNonEmptyString(x: unknown): x is string {
return typeof x === "string" && x.length > 0;
}
// 模板字面量类型
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">; // "onClick"
常用自定义工具类型
// 深可选(递归 Partial)
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};
// 深只读(递归 Readonly)
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
// 获取对象值的联合类型
type ValueOf<T> = T[keyof T];
// 联合转交叉
type UnionToIntersection<U> = (U extends any ? (x: U) => any : never) extends (x: infer I) => any ? I : never;
// 可空转必有默认(给可选属性加默认值时的辅助)
type WithDefault<T, K extends keyof T, D extends Pick<T, K>> = Omit<T, K> & D;
其它
对防抖函数进行类型标注
先写一个防抖函数
function sum(a: number, b: number) {
return a + b;
}
// 防抖函数
function debounce(fn, delay) {
let timer = null;
return function (...args) {
clearTimeout(timer); // 清除上一次的定时器,停止上一次即将执行的函数
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
防抖函数的第一个参数(函数)和它返回值(函数)格式是一样的。
// 粗略
declare function debounce(fn: Function, delay: number): Function;
// 精确(使用泛型)
declare function debounce<T extends Function>(fn: T, delay: number): T;
const dSum = debounce(sum, 1000);
// 但是存在一个问题,debounce 处理后的函数不应该有返回值,即 dSum 的返回值类型应该为 void
// 解决办法
declare function debounce<T extends any[]>(fn: (...args: T) => any, delay: number): (...args: T) => void;
前置的不定量参数
// 对这个函数进行类型标注
// a,b,c 分别对应 boolean,number,string 类型
addImpl("boolean", "number", "string", (a, b, c) => {});
type JSTypesMap = {
boolean: boolean;
number: number;
string: string;
symbol: symbol;
bigint: bigint;
undefined: undefined;
object: object;
function: Function;
};
type JSTypes = keyof JSTypesMap; // ['boolean', 'number', 'string', 'symbol', 'bigint', 'undefined', 'object', 'function']
type Transfer<T extends JSTypes[]> = {
[P in keyof T]: JSTypesMap[T[P]];
};
declare function addImpl<T extends JSTypes[]>(...args: [...T, (...args: Transfer<T>) => any]): void;
模板字符串类型
类似于模板字符串,它可以实现对字面量类型的计算,以及批量生成字符串类型的能力
type Name = "linbudu";
// "Hi, linbudu"
type Greeting = `Hi, ${Name}`;