Typescript
Typescript 类型
基础类型
- number
- string
- boolean
- symbol
- bigint
- null
- undefined
- object
- array
复合类型
数组
Array<number> 或 number[]
元组
[number, string]
const tuple: [number, string] = [1, "a"];
console.log(tuple[0]); // 1
console.log(tuple[1]); // a
类
class Person {}
特殊类型
any
任何类型都可以赋值给 any 类型.
unknown
unknown 类型是 any 类型的安全版本, 只能赋值给 unknown 类型或者 any 类型.
never
never 类型是其他类型的子类型, 表示一个永远不存在的值的类型.
void
void 类型是其他类型的子类型, 表示没有任何类型.
interface
// 定义对象类型
interface Person {
name: string;
age: number;
}
// 定义函数类型
interface Add {
(a: number, b: number): number;
}
// 构造器
interface PersonConstructor {
new (name: string, age: number): Person;
}
interface 的合并
当定义两个或多个同名的 interface 时, 它们会被合并为一个 interface, 它们的属性会被合并.
interface Person {
name: string;
}
interface Person {
age: number;
}
const person: Person = {
name: "John",
age: 30,
};
interface 和 type 的区别
interface 是一种类型的定义, 而 type 只是类型的别名, 这是它们的本质区别.
interface 是专门用来定义对象结构的, type 可以用来给任何类型起名字,包括对象、基本类型、联合类型、交叉类型等。
interface Person {
name: string;
}
type PersonType = {
name: string;
};
type status = "success" | "error";
type arr = number[];
| 特性 | interface | type |
|---|---|---|
| 描述对象 | ✅ | ✅ |
| 扩展(继承) | ✅(extends) | ✅(&) |
| 声明合并 | ✅ | ❌ |
| 支持泛型 | ✅ | ✅ |
| 联合类型 | ❌ | ✅ |
| 元组、基本类型 | ❌ | ✅ |
被类 implements | ✅ | ❌ |
interface 可选属性与修饰符
interface 使用 ? 定义可选属性, 使用 readonly 表示该属性或方法只读.
interface People {
name: string;
age: number;
job?: string;
readonly gender: string;
}
const people: People = {
name: "name",
age: 18,
gender: "male",
};
people.gender = "female"; // 报错, 只读属性不能被修改
枚举
枚举不仅仅是类型, 而且是常量. 其他类型在编译后会被删除, 但是枚举是运行时真正存在的对象.
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
枚举值的自动计算
未初始化枚举值
若所有枚举值都未初始化, 则枚举值会从 0 开始自动计算. 第一个枚举值为 0, 往后递增
enum Direction {
Up,
Down,
Left,
Right,
}
console.log([Direction.Up, Direction.Down, Direction.Left, Direction.Right]); // [0, 1, 2, 3]
第一个枚举值初始化
若第一个枚举值为数值, 则枚举值会从第一个枚举值开始自动计算. 往后递增
enum Direction {
Up = 1,
Down,
Left,
Right,
}
console.log([Direction.Up, Direction.Down, Direction.Left, Direction.Right]); // [1, 2, 3, 4]
其他枚举值初始化
若第一个枚举值未初始化, 但有其他枚举值初始化为数值, 则为初始化前面的枚举值将从第一个为 0 开始自动计算, 往后递增.
初始化后面的枚举值将从初始化的枚举值开始自动计算, 往后递增.
enum Direction {
Up,
Down,
Left = 3,
Right,
}
console.log([Direction.Up, Direction.Down, Direction.Left, Direction.Right]); // [0, 1, 3, 4]
初始值为非数值
若枚举的初始值为非数值, 那么 ts 无法计算初始值时, 会要求其他枚举值必须初始化. 否则会报错.
若部分初始值为非数值, 但是有初始值为数值时, 为数值的枚举值后面的枚举值可以被自动计算.
// 报错: 枚举成员必须具有初始化表达式。
enum Direction {
Up = "UP",
Down,
Left,
Right,
}
// 可以被自动计算
enum Direction {
Up = "UP",
Down = 2,
Left,
Right,
}
console.log([Direction.Up, Direction.Down, Direction.Left, Direction.Right]); // ["UP", 2, 3, 4]
常量枚举
常量枚举是使用 const 关键字定义的枚举, 常量枚举只能作为类型使用.
常量枚举会在编译阶段被删除, 并且不会在运行时创建对象. 常量枚举成员在使用时会被内联到使用处.
注意: 常量枚举不能有计算成员
// 普通枚举
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
console.log(Direction); // {Up: "UP", Down: "DOWN", Left: "LEFT", Right: "RIGHT"}
// 常量枚举
const enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
console.log(Direction); // 报错, 不能作为值在运行阶段使用
// 不能使用计算成员
const enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "right".toUpperCase(), // 报错, 常量枚举不能有计算成员,
}
函数
函数的定义
// 书写函数时定义
const add = (a: number, b: number): number => {
return a + b;
};
// 完整定义函数类型
type AddFunction = (num1: number, num2: number) => number;
const add: AddFunction = (num1, num2) => {
return num1 + num2;
};
// 接口定义函数类型
interface AddFunction {
(num1: number, num2: number): number;
}
const add: AddFunction = (num1, num2) => {
return num1 + num2;
};
// 可选参数, 可选参数必须放在必须参数后面
type AddFunction = (num1: number, num2: number) => number;
// 定义剩余参数
type AddFunction = (num1: number, ...numbers: number[]) => number;
const add: AddFunction = (num1, ...numbers) => {
return num1 + numbers.reduce((a, b) => a + b, 0);
};
// 定义 this
interface People {
name: string;
age: number;
say: (this: People) => string;
}
const people: People = {
name: "张三",
age: 18,
say() {
return `My name is ${this.name}, I am ${this.age} years old.`;
},
};
people.say(); // "My name is 张三, I am 18 years old."
const say = people.say;
say(); // 报错, this 指向错误
函数的重载
函数重载是指对同一个函数的多次定义, 每次定义的参数类型和返回类型可能不同.
函数重载的目的是为了提高代码的可读性和可维护性.
function parseDate(date: Date): string; // 传入 Date 类型, 返回格式好的时间字符串
function parseDate(date: string): number; // 传入时间字符串, 返回时间戳
function parseDate(date: number): Date; // 传入时间戳, 返回 Date 对象
function parseDate(date: Date | string | number): string | number | Date {
if (date instanceof Date) return date.toLocaleString();
if (typeof date === "string") return new Date(date).getTime();
return new Date(date);
}
使用 interface 定义函数重载
interface ParseDate {
(date: Date): string;
(date: string): number;
(date: number): Date;
}
const parseDate: ParseDate = (date) => {
if (date instanceof Date) return date.toLocaleString();
if (typeof date === "string") return new Date(date).getTime();
return new Date(date);
};
泛型
泛型是指在定义函数、接口或类的时候, 不预先指定具体的类型, 而是在使用的时候再指定类型, 就像函数参数一样.
泛型的使用有利于提升代码的可复用性和可维护性.
在 interface 中使用
interface People<T> {
name: string;
age: number;
job: T;
}
enum JOB {
FRONTEND_DEVELOPER,
BACKEND_DEVELOPER,
}
const eno: People<JOB> = {
name: "eno",
age: 18,
job: JOB.FRONTEND_DEVELOPER,
};
在函数中使用
const fetchData = <P, T>(url: string, params: P): T => {
const res = http.get(url, params);
return res.data as T;
};
const list: number[] = fetchData("url", "str"); // const fetchData: (url: string, params: string) => number[]
使用默认值
const fetchData = <P = string, T = number[]>(url: string, params: P): T => {
const res = http.get(url, params);
return res.data as T;
};
const list: number[] = fetchData("url", "str"); // const fetchData: (url: string, params: string) => number[]
对泛型的限制
使用 extends 可以限制泛型的类型
interface HttpResponseData {
code: string;
message: string;
}
// 限定 params 的类型 P 必须符合 object, 返回值 T 的类型必须符合 HttpResponseData
const fetchData = <P extends object, T extends HttpResponseData>(
url: string,
params: P
): T => {
const res = http.get(url, params);
return res.data as T;
};
// 错误写法
fetchData<string, number[]>("url", "str"); // 报错: string 不满足约束 object, number[] 不满足约束 HttpResponseData
// 正确写法
interface Params {
search: string;
}
interface ResponseData {
code: string;
data: number[];
message: string;
}
fetchData<Params, ResponseData>("url", { search: "search value" }); // const fetchData: (url: string, params: Params) => ResponseData
交叉类型和联合类型
交叉类型
将多个类型合并成一个类型, 新类型同时具有多个类型的所有属性
interface Person {
name: string;
age: number;
}
interface People {
name: string;
gender: "male" | "female";
}
type Human = Person & People; // {name: string; age: number; gender: 'male' | 'female'}
const eno: Human = {
name: "eno",
age: 18,
gender: "male",
};
联合类型
用于表示一个类型有可能是多个类型中的一个
`type UnionType = A | B;
联合类型 UnionType 表示一个类型可能是 A 类型也可能是 B 类型.
当访问联合类型数据属性时, 只能访问多个类型中共同拥有的属性, 只要有一个类型不具备该属性, 就无法从联合类型数据中直接获取属性值. 所以使用联合类型时经常使用断言来确定类型
interface Teacher {
name: string;
workCode: string;
}
interface Student {
name: string;
studentCode: string;
}
type Member = Teacher | Student;
const eno: Member = {
name: "eno",
workCode: "12138",
};
const printMember = (member: Member) => {
console.log(member.name); // 正常
console.log(member.workCode); // 报错
console.log(member.studentCode); // 报错
if ((member as Teacher).workCode) {
const { name, workCode } = member as Teacher; // 使用断言告诉 TS 这个数据是 Teacher 类型
return `My name is ${name}, I am teacher, code: ${workCode}`;
} else {
const { name, studentCode } = member as Student;
return `My name is ${name}, I am student, code: ${studentCode}`;
}
};
内置高级类型
| 类型 | 说明 |
|---|---|
| Record | 使用两个泛型表示 key 和 value 来构建对象类型 |
| Readonly | 把所有属性变为只读 |
| Partial | 把必选变为可选选 |
| Required | 把可选变为必选 |
| Pick | 从一个类型中选择一些属性, 构建新类型 |
| Omit | 从一个类型中剔除一些属性, 构建新类型 |
| Exclude | 从一个联合类型中剔除一些类型 |
| Extract | 从一个联合类型中提取一些类型 |
| Parameters | 获取函数参数类型 |
| ReturnType | 获取函数返回值类型 |
| InstanceType | 获取构造函数返回值类型 |
| ConstructorParameters | 用于提取构造器参数的类型 |
| Awaited | 用于获取 Promise 类型的返回值类型 |
| Uppercase | 用于将字符串类型转换为大写 |
| Lowercase | 用于将字符串类型转换为小写 |
| Capitalize | 用于将字符串类型的首字母转换为大写 |
| Uncapitalize | 用于将字符串类型的首字母转换为小写 |
| NonNullable | 用于从联合类型中剔除 null 和 undefined |
| ThisType | 用于指定函数中 this 的类型 |
// Record
type IObject = Record<string, number>;
// 等价于
interface IObject {
[key: string]: number;
}
// Readonly
interface Person {
name: string;
age: number;
job: {
title: string;
salary: number;
};
}
type ReadonlyPerson = Readonly<Person>;
// 等价于
interface ReadonlyPerson {
readonly name: string;
readonly age: number;
readonly job: {
title: string;
salary: number;
};
}
// Partial
interface Person {
name: string;
age: number;
job: {
title: string;
salary: number;
};
}
type PartialPerson = Partial<Person>;
// 等价于
interface PartialPerson {
name?: string;
age?: number;
job?: {
title?: string;
salary?: number;
};
}
// Required
interface Person {
name?: string;
age?: number;
job?: {
title?: string;
salary?: number;
};
}
type RequiredPerson = Required<Person>;
// 等价于
interface RequiredPerson {
name: string;
age: number;
job: {
title?: string;
salary?: number;
};
}
// Pick
interface Person {
name: string;
age: number;
job: {
title: string;
salary: number;
};
}
type PickPerson = Pick<Person, "name" | "age">;
// 等价于
interface PickPerson {
name: string;
age: number;
}
// Omit
interface Person {
name: string;
age: number;
job: {
title: string;
salary: number;
};
}
type OmitPerson = Omit<Person, "name" | "age">;
// 等价于
interface OmitPerson {
job: {
title: string;
salary: number;
};
}
// Exclude
type Status = "success" | "failed" | "pending" | "loading";
type NotFailedStatus = Exclude<Status, "failed">; // "success" | "pending" | "loading"
// Extract
type Status = "success" | "failed" | "pending" | "loading";
type OtherStatus = "success" | "failed" | "done";
type ExtractStatus = Extract<Status, OtherStatus>; // "success" | "failed"
// Parameters & ReturnType
type Func = (name: string, age: age) => { name: string; age: number };
type FuncParams = Parameters<Func>; // [name: string, age: number]
type FuncReturnType = ReturnType<Func>; // {name: string; age: number}
// ConstructorParameters & InstanceType
interface PersonConstructor {
new (name: string, age: number): { name: string; age: number };
}
type PersonConstructorParameters = ConstructorParameters<PersonConstructor>; // [name: string, age: number]
type PersonInstanceType = InstanceType<PersonConstructor>; // { name: string; age: number}
// Awaited
interface Person {
name: string;
age: number;
}
type PromisePerson = Promise<Person>;
type _Person = Awaited<PromisePerson>; // Person
// Uppercase & Lowercase & Capitalize & Uncapitalize
const name: Uppercase<string> = "ENO";
const name: Lowercase<string> = "eno";
const name: Capitalize<string> = "Eno";
const name: Uncapitalize<string> = "eno-Zeng";
// NonNullable
type Data = string | null | undefined;
type NonNullData = NonNullable<Data>; // string
// ThisType
interface Data {
count: number;
}
interface Methods {
getCount: () => number;
}
interface Store {
data: Data;
methods: Methods & ThisType<Data & Methods>;
}
const store: Store = {
data: { count: 0 },
methods: {
getCount() {
return this.count;
},
},
};
store.methods.getCount.call({ ...store.data, ...store.methods });
TS 类型体操与 extends & infer
什么是类型体操
TS 类型体操是 TypeScript 社区里的一个术语, 指的是利用 TS 类型推导从而获取一个新的类型的过程. 可以理解成在类型层面的逻辑运算.
extends
上面泛型中有提到 extends 关键字, 它可以用于泛型的类型约束, 此外 extends 也是 interface 继承的关键字. extends 还有更高级的用法就是用于条件类型判断, 条件类型是 TS 中非常重要的概念, 它可以根据类型的特性来进行类型推导.
举个例子: 假设一个 getInfo 函数, 参数可能是 User 或 Admin 类型, 根据不同的类型返回 UserInfo 或 AdminInfo 类型.
interface User {}
interface Admin {}
interface UserInfo {}
interface AdminInfo {}
const getInfo = <T extends User | Admin>(
user: T
): T extends User ? UserInfo : AdminInfo => {};
const userInfo = getInfo({} as User); // UserInfo
const adminInfo = getInfo({} as Admin); // AdminInfo
infer
infer 用于在类型推导中声明一个待推导的局部类型变量. infer 语句作为**条件类型(extends ? :)**的子语句, 只能在条件类型语句中使用.
infer 在 TS 的类型运算中有很重要的作用
举个例子: 比如我们有一个 List 类型, 我们想要获取 List 类型, 我们想要提取 List 类型中的元素类型, 可以使用 infer 语句.
type List = Array<number>;
type ListItem = List extends Array<infer Item> ? Item : never; // number
实现提取 Promise 类型中的返回值类型
TS 提供了 Awaited 类型, 用于获取 Promise 类型的返回值类型. 我们可以使用 infer 语句来实现 Awaited 类型.
type AwaitedPromise<T> = T extends Promise<infer P> P extends Promise<infer U> ? U : P : T;
在 extends 的条件语句中, 使用 infer 推导出 Promise 的泛型 P, 若 P 是 Promise 类型, 则继续递归调用 AwaitedPromise, 否则返回 P.
实现深度 readonly
TS 提供了 Readonly 类型, 用于将一个类型的所有属性变为只读. 但是对于对象嵌套的情况, Readonly 类型只能将第一层属性变为只读, 对于嵌套的属性, Readonly 类型无法将其变为只读.
interface Person {
name: string;
age: number;
job: {
title: string;
salary: number;
};
}
type ReadonlyPerson = Readonly<Person>;
// 等价于
interface ReadonlyPerson {
readonly name: string;
readonly age: number;
readonly job: {
title: string;
salary: number;
};
}
使用 Readonly 处理 Person 类型, 可以将 Person 类型的所有属性变为只读, 但是对于 job 属性这种对象类型属性, 其属性并不会变为只读
type DeepReadonly<T extends object> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
type ReadonlyPerson = DeepReadonly<Person>;
// 等价于
interface ReadonlyPerson {
readonly name: string;
readonly age: number;
readonly job: {
readonly title: string;
readonly salary: number;
};
}
通过 extends 语句判断 T[K] 是否为 object 类型, 如果是, 则递归调用 DeepReadonly 类型, 来实现深层的 readonly.
实现下划线转驼峰
type CamelCase<T extends string> = T extends `${infer Left}_${infer Right}`
? `${Left}${Capitalize<Right>}` extends `${infer L}_${infer R}`
? CamelCase<`${L}_${R}`>
: `${Left}${Capitalize<Right>}`
: T;
type CamelCasePerson = CamelCase<"name_age_job">; // "nameAgeJob"
2869

被折叠的 条评论
为什么被折叠?



