TypeScript类型系统核心速通:从基础到常用复合类型包装类
一、TS的8大基础类型(继承自JS)
TS完全继承了JS的类型设计,核心基础类型有8个,全部是小写字母 (首字母大写的Number/String等是JS内置对象,不是类型!):
| 类型 | 说明 | 示例 |
|---|---|---|
boolean |
布尔值(true/false) | const isOk: boolean = true |
string |
字符串 | const name: string = "TS" |
number |
数字(整数/浮点数/十六进制) | const age: number = 20 |
bigint |
大整数(ES2020新增) | const num: bigint = 123n |
symbol |
唯一标识值 | const id: symbol = Symbol() |
object |
对象/数组/函数(狭义对象) | const arr: object = [1,2] |
undefined |
未定义值 | let x: undefined = undefined |
null |
空值 | const y: null = null |
关键注意点:
undefined/null默认可以赋值给任何类型(比如let n: number = null不报错),但开启strictNullChecks编译选项后,只能赋值给自身/any/unknown(推荐开启);bigint和number不兼容(const x: bigint = 123会报错);object类型不包含原始类型(字符串/数字等),只包含对象、数组、函数。
ts
// tsconfig.json
开启:
{
"compilerOptions": {
"strictNullChecks": true
}
}
// 示例
let n: number = null; // 会报错,提示 "Type 'null' is not assignable to type 'number'."
let m: number | null = null; // 正确,允许为 null
二、包装类与大小写类型的核心区别
最容易踩坑的点就是「大小写类型」(比如string 与 String),而这一切的根源是JS的包装对象机制,先搞懂包装对象,再看类型就一目了然。
1. 什么是JS的包装对象?
JS中boolean、string、number、bigint、symbol这5种原始类型的值,本身没有方法(比如字符串的charAt()),但调用方法时,JS会自动把原始值转为"包装对象",执行完再销毁:
javascript
'hello'.charAt(1); // 实际执行:new String('hello').charAt(1) → 'e'
let num = 123;
console.log(num.toFixed(2)); // 实际执行:new Number(123).toFixed(2) → '123.00'
let bool = true;
console.log(bool.toString()); // 实际执行:new Boolean(true).toString() → 'true'
其中:
symbol/bigint:无法通过new直接创建包装对象(new Symbol()会报错);string/number/boolean:可以通过new创建包装对象(new String('hi'))。
2. TS中大小写类型的核心差异
TS为了区分"原始值"和"包装对象",给5种原始类型分别设计了小写(仅原始值) 和大写(包含包装对象) 两种类型:
| 类型对 | 范围说明 | 使用建议 |
|---|---|---|
string/String |
string:仅字符串原始值;String:原始值+包装对象 |
只用string |
number/Number |
number:仅数字原始值;Number:原始值+包装对象 |
只用number |
boolean/Boolean |
boolean:仅布尔原始值;Boolean:原始值+包装对象 |
只用boolean |
bigint/BigInt |
无实际差异(因无法创建包装对象) | 只用bigint |
symbol/Symbol |
无实际差异(因无法创建包装对象) | 只用symbol |
代码示例:
typescript
// 1. string(小写):只接受原始值,拒绝包装对象
const s1: string = "hello"; // 正确(原始值)
const s2: string = new String("hello"); // 报错(包装对象)
// 2. String(大写):接受原始值+包装对象(但完全没必要用)
const s3: String = "hello"; // 正确
const s4: String = new String("hello"); // 正确
// 3. 案例:内置方法只认小写类型
const n1: number = 1;
const n2: Number = 1;
Math.abs(n1); // 正确(Math.abs参数是number)
Math.abs(n2); // 报错(不接受Number类型)
3. Object vs object(对象类型的大小写)
在 TypeScript 中,Object(大写)和 object(小写)有着明确的区别,这两者的核心差异在于它们所包含的值的范围。
1. Object(大写)
-
范围说明 :
Object是指 JavaScript 中所有对象的构造函数,并且在 TypeScript 中,它几乎包括所有类型的值,除了undefined和null。这意味着变量可以被赋值为任何非空值,包括基本数据类型(如数字、字符串、布尔值)和对象(如数组、函数)。示例:
typescriptlet x: Object; x = 123; // 允许 x = 'hi'; // 允许 x = []; // 允许 x = {}; // 允许 -
使用建议 :尽管
Object可以被用来表示几乎所有的值,但因为这个类型的范围过于宽泛,且它并没有提供类型检查的能力,所以 绝对不建议使用Object类型。使用它会导致类型安全性降低,容易在代码中引入潜在的错误或不确定性。
2. object(小写)
-
范围说明 :
object是一个更为狭义的类型,仅表示一组"对象"。它仅包含普通的对象、数组和函数,不包括基本的原始值(如数字、字符串、布尔值等)。示例:
typescriptlet y: object; y = {}; // 允许(普通对象) y = []; // 允许(数组) y = function() {}; // 允许(函数) y = 123; // 错误(原始值) y = 'hi'; // 错误(原始值) y = null; // 错误(null 不算对象) -
使用建议 :当你需要一个变量或参数的类型必须是一个对象时,你应该使用
object类型。这种类型约束允许更好的类型检查,能够有效防止不必要的运行时错误。
小结
| 类型 | 范围说明 | 使用建议 |
|---|---|---|
Object(大写) |
几乎包含所有值(除了 undefined 和 null),包括所有基本数据类型和对象。 |
绝对不要使用 |
object(小写) |
仅包含普通对象、数组和函数,不包含原始值。 | 日常对象约束使用 |
理解重点
Object的范围过于宽泛,难以进行有效的类型检查,使用时容易出现漏洞。object则是专门用于指出仅允许为对象的类型,可以在多个用例中提供更好的类型安全性和代码的可维护性。
三、常用复合类型:联合类型 & 交叉类型
1. 联合类型(|):"或"逻辑
用|分隔多个类型,表示值可以是其中任意一种,核心是"多选一"。
核心用法:
typescript
// 用法1:支持多类型入参
let id: string | number;
id = "1001"; // 正确(字符串)
id = 1001; // 正确(数字)
// 用法2:限定固定取值(枚举式约束,超实用)
type Gender = "male" | "female"; // 只能是这两个字符串
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE"; // 接口请求方法
type Status = "success" | "error" | "pending"; // 接口状态
// 用法3:兼容null/undefined(开启strictNullChecks后)
let userName: string | null; // 可以是字符串或null
userName = "张三"; // 正确
userName = null; // 正确
使用联合类型时,TS无法确定具体类型,需通过"类型守卫"
typescript
function printId(id: string | number) {
// 类型守卫:判断是否为字符串
if (typeof id === "string") {
console.log(id.toUpperCase()); // 此时id是字符串
} else {
console.log(id); // 此时id是数字
}
}
2. 交叉类型(&):"且"逻辑
用&分隔多个类型,表示值必须同时满足所有类型,核心是"全满足",主要用于组合对象类型。
核心用法:
typescript
// 用法1:组合多个对象类型
type User = { name: string; age: number };
type Contact = { phone: string; email: string };
// 交叉类型:同时拥有User和Contact的所有属性
type UserWithContact = User & Contact;
const user: UserWithContact = {
name: "张三",
age: 25,
phone: "13800138000",
email: "zhangsan@example.com" // 必须包含所有属性
};
// 用法2:为已有类型新增属性
type BaseOrder = { orderId: string; amount: number };
// 新增折扣属性
type DiscountOrder = BaseOrder & { discount: number; total: number };
注意点:
如果交叉类型包含"同名不同类型"的属性,该属性会变成never(无法赋值):
typescript
type A = { age: number };
type B = { age: string };
type C = A & B;
// const c: C = { age: 18 }; // 报错(age类型为never)
四、提升效率的实用小技巧
1. type命令:给类型起别名
用type定义类型别名,简化复杂类型,提升代码可读性:
typescript
// 基础用法:简化基础类型
type Age = number;
let myAge: Age = 25;
// 实用用法:简化联合/交叉类型
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type PlainObject = Record<string, unknown>; // 约束仅普通对象
2. typeof运算符:提取变量的类型
TS扩展了typeof,可以"自动提取变量的类型",不用手动写:
typescript
const obj = { name: "TS", version: 5.0 };
// 自动提取obj的类型:{ name: string; version: number }
type ObjType = typeof obj;
// 直接使用提取的类型
let newObj: ObjType = { name: "JS", version: 6.0 }; // 符合类型
五、核心总结
- TS基础类型共8个(全小写),
undefined/null开启strictNullChecks更安全; - 包装类是JS的底层机制,TS用"小写类型(仅原始值)"和"大写类型(含包装对象)"区分,只用小写类型;
- 对象类型只选
object(小写),Object(大写)范围过宽无意义; - 联合类型(|)是"或"逻辑(多选一),交叉类型(&)是"且"逻辑(全满足);
type别名简化复杂类型,typeof提取变量类型,提升开发效率。