Welcome to TypeScript World — TypeScript 基础类型入门全指南
从 JavaScript 到 TypeScript,你的第一步从类型系统开始。本文带你从零掌握 TypeScript 所有基础类型,配合在线实验和实战挑战,真正理解每个类型的使用场景。
目录
一、为什么需要 TypeScript?
如果你写过 JavaScript,一定经历过这种痛苦:
// JavaScript 的噩梦
function getUser(data) {
return data.name.toUpperCase(); // 运行时突然报错:Cannot read 'name' of undefined
}
这段代码在编译期没有任何问题,但一运行就崩溃。JavaScript 是动态类型语言,类型错误只能在运行时暴露,这意味着:
你需要手动
console.log调试类型问题重构代码时不敢动,怕改出 bug
团队协作时不知道函数参数是什么类型
IDE 自动补全经常猜错
TypeScript 的解决方案:把类型检查提前到编译期。在代码运行之前,就告诉你哪里有问题。
// TypeScript 的保护
function getUser(data: { name: string }): string {
return data.name.toUpperCase(); // ✅ 类型安全,编译期保证 data.name 存在且是 string
}
二、TypeScript 与 JavaScript 的关系
一句话总结:
TypeScript = JavaScript + 类型系统
具体来说:
TS 是 JS 的超集意味着:
所有合法的 JavaScript 代码都是合法的 TypeScript 代码
TypeScript 额外增加了类型注解语法
TypeScript 编译后会擦除所有类型信息,输出纯 JavaScript
// 这段 TS 代码
const name: string = "张三";
console.log(name);
// 编译后输出的 JS 代码(类型被擦除)
const name = "张三";
console.log(name);三、环境准备:TypeScript Playground
无需安装任何东西,直接打开浏览器即可开始学习。
3.1 访问 Playground
3.2 界面说明
3.3 重要提示
如果你遇到以下报错:
Cannot redeclare block-scoped variable 'name'.
原因:TypeScript 默认把 Playground 中的代码当作"全局脚本"处理,而浏览器的全局作用域中已存在 window.name 属性,产生冲突。
解决方法:在代码最顶部加一行 export {};,告诉 TS 这是独立模块:
export {}; // 让 TS 把这段代码当作模块处理
let name: string = "张三"; // 不再报错四、基础原始类型
TypeScript 最基础的类型是三种原始类型:string、number、boolean。
4.1 string — 字符串
let userName: string = "张三";
let message: string = `Hello, ${userName}`; // 模板字符串也是 string
4.2 number — 数字
let age: number = 28; // 十进制
let hex: number = 0xff; // 十六进制
let binary: number = 0b1010; // 二进制
let octal: number = 0o744; // 八进制
// 整数、小数、负数、十六进制、二进制,全是 number
4.3 boolean — 布尔
let isDeveloper: boolean = true;
let hasError: boolean = false;
4.4 类型注解语法
// 语法:变量名: 类型 = 值
let 变量名: 类型 = 值;
4.5 动手实验
在 Playground 中输入以下代码,然后故意写错,观察报错信息:
export {};
let name: string = "张三";
let age: number = 28;
let isDeveloper: boolean = true;
// 🔥 实验1:把 number 赋值给 string
name = 28; // Error: Type ‘number’ is not assignable to type ‘string’.
// 🔥 实验2:把 string 赋值给 number
age = "28"; // Error: Type ‘string’ is not assignable to type ‘number’.观察要点:左侧红色波浪线标注错误位置,鼠标悬停可看到详细错误信息。TypeScript 的报错信息非常清晰,告诉你"什么类型不能赋值给什么类型"。
五、数组与元组
5.1 数组(Array)
数组有两种写法:
// 写法1:类型后面加方括号(推荐)
let skills: string[] = ["TypeScript", "React", "Vue"];
// 写法2:泛型写法
let nums: Array<number> = [1, 2, 3];
// 两种写法完全等价,选你喜欢的方式即可数组中所有元素必须是同一种类型:
let scores: number[] = [90, 85, 92];
scores.push(88); // ✅ 正确
scores.push("88"); // ❌ Error: Argument of type 'string' is not assignable to parameter of type 'number'.
5.2 元组(Tuple)
元组和数组不同——它每个位置的类型是固定的。
// 元组:固定长度,每位置有固定类型
let person: [string, number] = ["李四", 30];
// 第一个必须是 string,第二个必须是 number
let person2: [string, number] = [30, "李四"]; // ❌ 顺序反了
let person3: [string, number] = ["李四", 30, true]; // ❌ 多了元素5.3 数组 vs 元组:什么时候用哪个?
元组的典型场景:
// 坐标
let coord: [number, number] = [120.5, 30.2];
// HTTP 响应 [状态码, 响应体]
let response: [number, string] = [200, "OK"];
// RGB 颜色
let color: [number, number, number] = [255, 128, 0];六、枚举
当一个变量的值只有几种固定的可能时,用枚举最合适。
6.1 为什么需要枚举?
// ❌ 用数字:不直观
let status1 = 0; // 0 是什么意思?待支付?还是已发货?
// ❌ 用字符串:容易拼错
let status2 = "peding"; // 拼写错误,编译期不会报错
// ✅ 用枚举:语义清晰,编译期检查
enum OrderStatus {
Pending,
Paid,
Shipped,
Completed,
}
let status3 = OrderStatus.Pending; // 语义清晰6.2 数字枚举(默认)
enum Direction {
Up, // 0(默认从 0 开始)
Down, // 1(自动递增)
Left, // 2
Right, // 3
}
console.log(Direction.Up); // 0
console.log(Direction[0]); // "Up" ← 反向映射(数字枚举特有)6.3 字符串枚举
enum Status {
Active = "ACTIVE",
Inactive = "INACTIVE",
Pending = "PENDING",
}
// 字符串枚举没有反向映射
console.log(Status.Active); // "ACTIVE"
console.log(Status["ACTIVE"]); // ❌ Error: 没有反向映射6.4 常量枚举(const enum)
const enum Color {
Red,
Green,
Blue,
}
let myColor = Color.Red;
// 编译后 → let myColor = 0; (直接内联,不生成对象)普通枚举 vs 常量枚举的编译差异:
// 普通枚举编译后:生成一个对象
var Direction;
(function (Direction) {
Direction[Direction["Up"] = 0] = "Up";
Direction[Direction["Down"] = 1] = "Down";
// ...
})(Direction || (Direction = {}));
// 常量枚举编译后:直接内联成字面量
let myColor = 0; // 不生成任何对象,性能更好6.5 枚举的最佳实践
优先用字符串枚举:调试时能看到有意义的值,而不是数字
性能敏感场景用 const enum:编译后零开销
考虑用联合字面量类型替代枚举(后续课程会讲):
type Status = "active" | "inactive" | "pending"; // 更轻量
七、any 与 unknown
any 和 unknown 都表示"任意类型",但安全性截然不同。
7.1 any — 放弃类型检查
let anything: any = 42;
anything = "hello"; // ✅ 不报错
anything.foo.bar.baz(); // ✅ 不报错(运行时可能崩溃!)
anything(); // ✅ 不报错any 就是"关闭 TypeScript 的类型检查",等于回到了 JavaScript 时代。听起来很自由,但很危险——运行时崩溃了,TS 不会提前提醒你。
7.2 unknown — 安全的 any
let notSure: unknown = 42;
notSure = "hello"; // ✅ 可以赋值
// ❌ 不能直接使用 unknown
notSure.foo(); // Error: Object is of type ‘unknown’.
notSure.toUpperCase(); // Error: Object is of type ‘unknown’.
// ✅ 必须先做类型收窄(narrowing)
if (typeof notSure === "string") {
console.log(notSure.toUpperCase()); // ✅ 安全!TS 知道这里是 string
}
if (typeof notSure === "number") {
console.log(notSure.toFixed(2)); // ✅ 安全!TS 知道这里是 number
}7.3 any vs unknown 对比
最佳实践:优先用 unknown,只在万不得已时用 any。
八、void、null、undefined、never
这四个特殊类型各有用途。
8.1 void — 没有返回值
function logMessage(msg: string): void {
console.log(msg);
// 没有 return 语句,返回值类型是 void
}
void 主要用于函数返回值类型标注,表示"这个函数不返回任何有意义的值"。
8.2 null 和 undefined
let u: undefined = undefined;
let n: null = null;
在 strict 模式下(建议开启),null 和 undefined 是独立的类型,不能随意赋给其他类型:
// strict 模式下
let name: string = null; // ❌ Error
let age: number = undefined; // ❌ Error
// 如果确实需要允许 null,用联合类型
let name: string | null = null; // ✅8.3 never — 永远不会出现的值
never 是最特殊的类型,表示"永远不会到达"。
场景1:函数永远抛出错误
function throwError(message: string): never {
throw new Error(message); // 函数不会正常返回
}
场景2:死循环
function infiniteLoop(): never {
while (true) {} // 永远不会结束
}
场景3:穷尽性检查(最重要!)
type Shape = "circle" | "square" | "triangle";
function getArea(shape: Shape): number {
switch (shape) {
case "circle": return Math.PI;
case "square": return 4;
case "triangle": return 2;
default:
// 如果 Shape 新增了类型(比如 "hexagon"),
// 但这里没有处理,TS 会编译报错!
const _exhaustive: never = shape;
return _exhaustive;
}
}穷尽性检查的威力:当你修改 Shape 类型新增一个值时,default 分支的 _exhaustive: never = shape 会报错,提醒你补全 switch 分支。这是 TypeScript 编译期保护你的利器。
8.4 四种特殊类型对比
九、类型推断
TypeScript 足够聪明,很多时候你不需要显式写类型注解。
9.1 变量类型推断
let name = "王五"; // TS 推断为 string
let age = 25; // TS 推断为 number
let isOnline = false; // TS 推断为 boolean
let hobbies = ["游泳", "编程"]; // TS 推断为 string[]
9.2 最佳实践:什么时候写注解?
// ✅ 能推断的就不写(简洁)
let message = "Hello TS";
// ❌ 多余的注解
let message: string = "Hello TS";
// ✅ 函数参数必须写类型注解(TS 无法推断参数类型)
function greet(name: string, age: number): string {
return 你好,${name},你今年 ${age} 岁;
}
// ❌ 不写参数类型会报错
function greet(name, age) { // Error: Parameter ‘name’ implicitly has an ‘any’ type.
return 你好,${name};
}9.3 类型推断规则速查
let vs const 的推断差异:
let x = "hello"; // 推断为 string(宽松,因为 let 可重新赋值)
const y = "hello"; // 推断为 "hello"(精确字面量,因为 const 不可变)
十、常见坑点与最佳实践
坑点1:Playground 全局变量冲突
// ❌ 报错:Cannot redeclare block-scoped variable 'name'
let name: string = "张三";
// ✅ 加 export {} 解决
export {};
let name: string = "张三";坑点2:数组推断了联合类型
let arr = [1, "hello", true];
// arr 的类型是 (string | number | boolean)[]
// 不是 string[] 或 number[]
坑点3:枚举的运行时存在
enum Status { Active, Inactive }
// 枚举编译后会生成真实对象,占运行时
console.log(Status); // { ‘0’: ‘Active’, ‘1’: ‘Inactive’, Active: 0, Inactive: 1 }
// 如果不想占运行时,用 const enum
const enum Status2 { Active, Inactive }
// 编译后完全消失,直接内联为数字坑点4:any 会传染
let data: any = getData();
let name = data.name; // name 的类型也是 any
let age = name.age; // age 的类型也是 any
// any 会沿着调用链一路传播,类型安全防线崩溃
最佳实践清单
始终开启
strict模式:享受完整的类型保护函数参数必须写类型注解:TS 无法推断参数类型
变量能推断就不写注解:减少冗余代码
优先用
unknown而非any:安全第一用
never做穷尽性检查:防止 switch 漏分支字符串枚举优于数字枚举:调试更友好
性能敏感处用
const enum:编译后零开销
十一、自测挑战
把以下代码复制到 TypeScript Playground,找出并修复所有类型错误:
export {};
// 🎯 挑战:这段代码有 5 个类型错误,全部找出来并修复!
let userName = "小明";
userName = 123; // 错误1
let scores: number[] = [90, 85, 92];
scores.push("88"); // 错误2
let config: [string, boolean] = ["dark", true];
config[1] = "light"; // 错误3
enum Role { Admin = "ADMIN", User = "USER" }
let myRole: Role = "ADMIN"; // 错误4
let data: unknown = "hello";
console.log(data.toUpperCase()); // 错误5参考答案
点击展开答案
export {};
// 修复1:userName 是 string 类型,不能赋值 number
let userName = "小明";
// 方案A:改值
// userName = "123";
// 方案B:改类型
let userName2: string | number = "小明";
userName2 = 123;
// 修复2:scores 是 number[],不能 push string
let scores: number[] = [90, 85, 92];
scores.push(88); // 改为数字
// 修复3:config 是元组 [string, boolean],第二个位置必须是 boolean
let config: [string, boolean] = ["dark", true];
config[1] = false; // 改为 boolean 值
// 修复4:myRole 的类型是 Role 枚举,必须用 Role.xxx
enum Role { Admin = "ADMIN", User = "USER" }
let myRole: Role = Role.Admin; // 用枚举成员
// 修复5:unknown 类型不能直接调用方法,必须先类型收窄
let data: unknown = "hello";
if (typeof data === "string") {
console.log(data.toUpperCase()); // 收窄后才能调用
}十二、总结与知识图谱
基础类型全景图
TypeScript 基础类型
├── 原始类型
│ ├── string — 字符串
│ ├── number — 数字(整数/小数/负数/进制)
│ └── boolean — 布尔(true/false)
├── 复合类型
│ ├── Array — 数组(T[] 或 Array<T>)
│ └── Tuple — 元组(固定长度和类型)
├── 枚举
│ ├── 数字枚举(默认从 0 递增,有反向映射)
│ ├── 字符串枚举(手动赋值,无反向映射)
│ └── const enum(编译后内联,零开销)
├── 任意类型
│ ├── any — 放弃检查(危险)
│ └── unknown — 安全的 any(必须收窄)
└── 特殊类型
├── void — 无返回值
├── null — 空值
├── undefined — 未定义
└── never — 永不出现(抛错/死循环/穷尽检查)
速记口诀
学习成果检查
学完本文后,你应该能够:
[ ] 说出 TypeScript 所有基础类型及其使用场景
[ ] 区分数组和元组的适用场景
[ ] 解释
any和unknown的区别[ ] 理解
never类型的三种使用场景[ ] 知道什么时候写类型注解,什么时候靠推断
[ ] 在 Playground 中独立完成类型实验
推荐资源
下一步
本文是 TypeScript 深入 系列的第 1 天。接下来的学习路线:
第 2 天:接口 vs 类型别名 — 两种定义对象形状的方式
第 3 天:联合类型与交叉类型 — 类型组合的艺术
第 4 天:类型守卫与类型断言 — 安全的类型收窄
第 5 天:枚举与字面量类型 — 定海神针
第 6 天:keyof / typeof / 索引访问 — 类型钥匙
第 7 天:第一关 BOSS 战 — 综合实战
学编程就像蜗牛往上爬,慢一点没关系,关键是不停下来。
每天花 2 小时,28 天通关 TypeScript 深入。加油!
本文是「Web 前端工业可视化学习」路线的 TypeScript 阶段第 1 天内容。更多学习内容请关注专栏更新。