2、基础类型系统:布尔、数字、字符串、数组、元组、枚举、any、void、null、undefined、never
好,咱们正式开始聊 TypeScript 的类型系统。
说实话,我刚从 JavaScript 转到 TypeScript 时,最直观的感受就是:终于有人管管变量的类型了。以前写 JS,一个变量今天是个字符串,明天可能就变成了对象,调试起来简直要命。TypeScript 的基础类型,说白了就是给变量加了个「身份证」,让代码更靠谱。
2.1 布尔、数字、字符串:三剑客
这三个是最常用的基础类型,写法也最直观。
let isDone: boolean = false;
let age: number = 28;
let name: string = '张三';
我个人习惯,能用 const 就尽量不用 let。为什么呢?因为 const 声明的字面量,TypeScript 能自动推断出更精确的类型,而不是宽泛的 string 或 number。
0b1010 就是 10,0xff 就是 255。我在处理颜色值的时候经常用十六进制。
字符串拼接这块,TypeScript 完全支持模板字符串,跟 ES6 一样。你想想看,写 SQL 拼接或者生成 HTML 片段时,用反引号加 ${} 多舒服。
2.2 数组与元组:有序集合的两种姿态
数组有两种定义方式,我推荐用泛型写法,更统一。
// 方式一:元素类型后加 []
let list1: number[] = [1, 2, 3];
// 方式二:泛型数组(我更喜欢这种)
let list2: Array<number> = [1, 2, 3];
元组就更有意思了。它允许你定义一个固定长度、每个位置类型已知的数组。
let tuple: [string, number] = ['hello', 42];
// 正确
tuple[0] = 'world';
tuple[1] = 100;
// 错误:类型不匹配
// tuple[0] = 100; // 报错
我在项目中遇到过一个问题:用元组表示 CSV 的一行数据,比如 [name, age, score]。这样每个字段的类型都明确,比用普通数组安全得多。
.push() 添加元素,但访问越界元素会报错。嗯,这里要小心,别以为元组长度是固定的就完全不能变了。
2.3 枚举:给数字起个名字
枚举是我非常喜欢的一个特性。它让代码的可读性直接拉满。
enum Direction {
Up = 1,
Down,
Left,
Right
}
let dir: Direction = Direction.Up;
默认情况下,枚举成员从 0 开始自增。但你可以手动赋值,就像上面我让 Up = 1,后面的就依次是 2、3、4。
我曾经在写状态机时,用枚举表示各种状态:Pending、Approved、Rejected。比起用魔法数字 0、1、2,枚举让代码自文档化,别人一看就懂。
Direction[1] 会返回 'Up'。但要注意,常量枚举(加 const 关键字)会在编译时被内联,不会生成对象,性能更好。
2.4 any、void、null、undefined、never:特殊类型
这几个类型各有各的用途,咱们一个一个说。
any:万能逃生舱
any 类型可以赋任何值,也可以调用任何方法。说白了,就是「放弃类型检查」。
let notSure: any = 4;
notSure = 'maybe a string';
notSure = false; // 完全没问题
我建议:能不用就不用。用了 any 就等于把 TypeScript 降级成 JavaScript。但有时候,比如对接第三方库没有类型定义,或者渐进式迁移老项目,any 确实是个好用的逃生舱。
void:空返回值
void 表示没有返回值。通常用在函数声明上。
function warnUser(): void {
console.log('This is a warning message');
}
你可能会问:void 和 undefined 有什么区别?void 类型的变量只能赋 undefined 或 null(严格模式下只能 undefined)。但 void 更多是作为函数返回类型的语义标记。
null 和 undefined:两个小老弟
在 TypeScript 中,null 和 undefined 各自有各自的类型。默认情况下,它们是所有类型的子类型,可以赋值给任何类型。但如果你开启了 --strictNullChecks(我强烈建议开启),它们就只能赋值给 any 和它们自己了。
let u: undefined = undefined;
let n: null = null;
// 开启 strictNullChecks 后,下面会报错
// let age: number = null; // 报错
我曾经因为没开 strictNullChecks,在线上出了个 bug:一个函数返回 number,但实际返回了 null,导致后续计算全崩。从那以后,我所有项目都默认开启这个选项。
never:永不存在的类型
never 表示永远不会发生的值。比如一个函数永远抛异常,或者无限循环。
function error(message: string): never {
throw new Error(message);
}
function infiniteLoop(): never {
while (true) {}
}
never 是 TypeScript 类型系统的底层基石之一。它在联合类型中会被自动忽略,比如 string | never 的结果就是 string。你想想看,这其实很符合逻辑:一个永远不会出现的类型,在联合里自然就没意义了。
switch 或 if-else 的 default 分支里,把变量断言为 never。这样如果以后新增了枚举成员,TypeScript 会报错提醒你处理。这是「穷举检查」的经典用法。
2.5 类型推断:让 TypeScript 帮你省点事
TypeScript 能根据赋值自动推断类型,不需要你每次都写类型注解。
let someValue = 'hello'; // 自动推断为 string
let num = 42; // 自动推断为 number
我个人习惯:简单变量靠推断,函数参数和返回值写注解。这样既省事,又保证了接口的清晰。
2.6 小结
基础类型系统是 TypeScript 的根基。别看它们简单,但组合起来能表达非常丰富的语义。我见过不少新手一上来就追求高级类型,结果基础没打牢,写出来的代码反而更混乱。
记住一句话:类型系统不是束缚,而是你的帮手。它帮你提前发现错误,让代码自文档化,也让团队协作更顺畅。下一章咱们聊聊接口和对象类型,那才是真正开始玩花样的时候。