本文系统梳理 TypeScript 类型体系,覆盖原始类型、复合类型、内置工具类型到进阶类型机制,附代码示例与选型最佳实践,可作为前端 / 全栈开发的类型知识库归档。
一、为什么 TypeScript 需要类型系统
1.1 JavaScript 的动态类型痛点
JavaScript 是弱类型、动态类型语言,变量类型可以随时改变,错误只有运行时才会暴露:
js
运行
// JS 不会报错,运行时才会出问题
function add(a, b) {
return a + b;
}
add("1", 2); // 返回 "12",逻辑错误,编译期无任何提示
小型项目尚可接受,中大型项目中会导致:
- 重构困难,改一个字段全靠人肉搜索
- 接口字段不匹配,线上才爆错
- 代码可读性差,参数含义全靠猜
1.2 TypeScript 静态类型的核心价值
TypeScript = JavaScript + 静态类型系统,在编译阶段做类型检查,不运行代码就能发现错误。 核心价值:
- 提前捕获错误:编译期拦截类型不匹配、字段缺失等问题
- 提升可维护性:类型即文档,看类型就知道数据结构
- 智能提示更强:IDE 自动补全、跳转定义、重构更安全
- 团队协作更稳:统一数据契约,减少沟通成本
1.3 类型注解基本语法
在变量、函数参数、函数返回值后用 : 类型 标注:
typescript
运行
// 变量类型注解
let name: string = "张三";
let age: number = 25;
// 函数参数 + 返回值注解
function greet(name: string): string {
return `Hello, ${name}`;
}
TS 支持类型推断:能自动推导类型的场景,不需要手动写注解。
typescript
运行
// 自动推断为 string,无需手动写注解
let message = "hello world";
二、基础原始类型
TS 继承了 JS 的全部原始类型,并扩展了特殊类型。
2.1 常规原始类型
表格
| 类型 | 说明 | 示例 |
|---|---|---|
string |
字符串 | let s: string = "abc" |
number |
数字(整数、浮点数统一) | let n: number = 100 |
boolean |
布尔值 | let flag: boolean = true |
null |
空值 | let v: null = null |
undefined |
未定义 | let u: undefined = undefined |
symbol |
唯一符号 | let key: symbol = Symbol() |
bigint |
大整数 | let big: bigint = 100n |
注意:
null和undefined默认是所有类型的子类型(可赋值给任意类型),开启strictNullChecks后会严格限制,是生产环境推荐配置。
2.2 特殊原始类型
① any:任意类型
关闭类型检查,变量可以赋值任意类型,相当于退化成 JS。
typescript
运行
let data: any = "hello";
data = 123; // 不报错
data = true; // 不报错
data.xxx(); // 调用不存在的方法也不报错
危害:失去所有类型保护,是 TS 中的「后门」,非必要不使用。
② unknown:安全的任意类型
unknown 是类型安全的 any。可以接收任意值,但不能直接操作,必须先做类型判断 / 断言才能使用。
typescript
运行
let val: unknown = 100;
// val + 10; ❌ 直接报错,不能操作 unknown
if (typeof val === "number") {
console.log(val + 10); // ✅ 类型守卫后可用
}
选型建议 :不确定类型时,优先用 unknown 代替 any。
③ void:无返回值
用于函数,代表函数没有返回值。
typescript
运行
function log(msg: string): void {
console.log(msg);
// 不需要 return,或 return 空
}
④ never:永不存在的值
代表永远不会有返回的类型,常见两种场景:
- 抛出异常的函数
- 无限循环的函数
- 联合类型穷尽检查的兜底
typescript
运行
// 抛出异常,永远不会正常返回
function throwError(msg: string): never {
throw new Error(msg);
}
三、复合类型
3.1 数组类型
两种写法,效果完全一致,推荐第一种:
typescript
运行
// 写法1:类型 + [](最常用)
let nums: number[] = [1, 2, 3];
let names: string[] = ["a", "b"];
// 写法2:泛型数组
let nums2: Array<number> = [1, 2, 3];
3.2 元组 Tuple
固定长度、固定每个位置类型的数组。
typescript
运行
// 第一个元素 string,第二个 number,长度固定为2
let user: [string, number] = ["张三", 25];
// 场景:函数返回多个值
function getUserInfo(): [string, number] {
return ["李四", 30];
}
3.3 对象类型:type 与 interface
TS 有两种方式定义对象结构:type(类型别名)和 interface(接口)。
① type 类型别名
typescript
运行
type User = {
id: number;
name: string;
age?: number; // ? 代表可选属性
readonly email: string; // readonly 代表只读,不可修改
};
const u: User = {
id: 1,
name: "王五",
email: "wang@test.com"
};
② interface 接口
typescript
运行
interface Product {
id: number;
title: string;
price: number;
desc?: string;
}
const p: Product = {
id: 101,
title: "手机",
price: 2999
};
③ type vs interface 核心区别
表格
| 特性 | type | interface |
|---|---|---|
| 定义对象 | ✅ | ✅ |
| 定义联合 / 交叉 / 元组 / 函数类型 | ✅ | ❌ |
| 声明合并(重复定义自动合并) | ❌ | ✅ |
| 继承扩展 | 通过 & 交叉类型 |
通过 extends |
| 映射类型、条件类型 | ✅ | ❌ |
④ 选型建议
- 优先用 interface 定义对象、类的结构,扩展性更好,支持声明合并
- 用 type 定义联合类型、交叉类型、工具类型、元组、函数类型
- 简单对象二者均可,团队统一风格即可
3.4 联合类型与交叉类型
① 联合类型 |
变量可以是多种类型中的一种:
typescript
运行
let status: "pending" | "success" | "error"; // 字面量联合
status = "success";
let id: string | number;
id = 100;
id = "abc";
② 交叉类型 &
将多个类型合并为一个,拥有所有属性:
typescript
运行
type Base = { id: number };
type Time = { createTime: string };
type RecordItem = Base & Time; // 同时拥有 id 和 createTime
3.5 函数类型
完整描述参数类型和返回值类型:
typescript
运行
// 直接定义
type AddFn = (a: number, b: number) => number;
const add: AddFn = (a, b) => a + b;
// 可选参数
function greet(name: string, prefix?: string): string {
return prefix ? `${prefix} ${name}` : name;
}
// 剩余参数
function sum(...nums: number[]): number {
return nums.reduce((a, b) => a + b, 0);
}
3.6 枚举 enum
定义一组命名的常量,分为数字枚举和字符串枚举。
typescript
运行
// 数字枚举:默认从 0 开始自增
enum Status {
Pending, // 0
Success, // 1
Error // 2
}
// 字符串枚举
enum OrderStatus {
Created = "created",
Paid = "paid",
Shipped = "shipped"
}
注意:枚举会编译成实际的 JS 对象,增加代码体积。简单场景推荐用字面量联合类型替代枚举,更轻量。
四、内置高级工具类型(Utility Types)
TS 内置了一批常用的类型转换工具,基于泛型实现,日常开发高频使用。
4.1 Partial<T>:所有属性变为可选
typescript
运行
interface User { id: number; name: string; age: number }
type PartialUser = Partial<User>;
// 等价于 { id?: number; name?: string; age?: number }
场景:更新对象时,只传部分字段。
4.2 Required<T>:所有属性变为必填
typescript
运行
type RequiredUser = Required<PartialUser>;
// 所有可选属性变回必填
4.3 Readonly<T>:所有属性变为只读
typescript
运行
type ReadonlyUser = Readonly<User>;
// 所有属性只能读,不能修改
4.4 Pick<T, K>:选取部分属性
typescript
运行
type UserSimple = Pick<User, "id" | "name">;
// 等价于 { id: number; name: string }
场景:精简大对象,只取需要的字段。
4.5 Omit<T, K>:排除部分属性
typescript
运行
type UserWithoutAge = Omit<User, "age">;
// 等价于 { id: number; name: string }
4.6 Record<K, T>:定义键值对类型
typescript
运行
// 键是 string,值是 number
type ScoreMap = Record<string, number>;
const scores: ScoreMap = { math: 90, english: 85 };
4.7 Exclude<T, U>:联合类型排除
typescript
运行
type Status = "a" | "b" | "c";
type StatusWithoutC = Exclude<Status, "c">; // "a" | "b"
4.8 Extract<T, U>:联合类型提取交集
typescript
运行
type A = "a" | "b" | "c";
type B = "b" | "c" | "d";
type Common = Extract<A, B>; // "b" | "c"
4.9 ReturnType<T>:获取函数返回值类型
typescript
运行
function getUser() {
return { id: 1, name: "张三" };
}
type UserType = ReturnType<typeof getUser>;
// { id: number; name: string }
4.10 Parameters<T>:获取函数参数类型元组
typescript
运行
type AddParams = Parameters<typeof add>;
// [number, number]
五、进阶类型机制
5.1 类型守卫
缩小联合类型的范围,让 TS 识别具体类型:
typescript
运行
// typeof 守卫
function print(val: string | number) {
if (typeof val === "string") {
console.log(val.length); // 识别为 string
} else {
console.log(val.toFixed(2)); // 识别为 number
}
}
// in 守卫:判断对象是否包含某属性
interface Dog { bark(): void }
interface Cat { meow(): void }
function speak(animal: Dog | Cat) {
if ("bark" in animal) {
animal.bark();
} else {
animal.meow();
}
}
5.2 类型断言 as
手动告诉 TS 「我确定这个值是什么类型」,跳过检查。
typescript
运行
const input = document.getElementById("input") as HTMLInputElement;
input.value; // 直接访问 input 专属属性
注意:类型断言有风险,确保类型正确再使用,避免滥用。
5.3 条件类型
类似三元运算符,根据类型判断返回不同类型:
typescript
运行
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
内置工具类型大多基于条件类型实现。
5.4 映射类型
遍历已有类型的键,生成新类型:
typescript
运行
// 把所有属性变为 string
type AllString<T> = {
[K in keyof T]: string;
};
5.5 常用操作符
-
keyof:获取对象类型的所有键,返回联合类型typescript
运行
interface User { id: number; name: string } type Keys = keyof User; // "id" | "name" -
typeof:获取值的类型typescript
运行
const obj = { a: 1, b: "2" }; type ObjType = typeof obj; // { a: number; b: string } -
as const:将值断言为只读字面量类型typescript
运行
const arr = ["a", "b"] as const; type ArrType = typeof arr; // readonly ["a", "b"]
六、最佳实践与避坑
- 尽量不用
any,不确定类型优先用unknown+ 类型守卫 - 对象优先
interface,联合 / 工具类型用type,保持风格统一 - 开启严格模式 :
tsconfig.json中strict: true,获得最强类型检查 - 避免过度类型体操:过于复杂的嵌套类型会降低可读性,适当拆分
- 少用枚举:简单场景用字面量联合类型,更轻量无运行时代价
- 类型断言要谨慎:只在你比 TS 更清楚类型时使用,不要用来「掩盖」错误
七、全文总结
TypeScript 的类型系统是其核心价值所在:
- 基础类型 覆盖 JS 全部原始值,补充了
any/unknown/never/void等特殊类型 - 复合类型通过对象、数组、元组、联合、交叉描述复杂数据结构
- 内置工具类型提供开箱即用的类型转换能力,覆盖 90% 日常场景
- 进阶机制支持条件、映射等高级操作,满足复杂类型需求
合理使用类型系统,能在不损失 JS 灵活性的前提下,大幅提升代码质量、可维护性与团队协作效率,是中大型前端项目的标配。