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:编译后零开销
十一、实际场景:这些类型到底用在哪?
学完基础类型后,你可能觉得"知道了是什么,但不知道什么时候用"。这一节结合你正在学习的工业可视化方向,用真实开发场景把每个类型落到实处。
11.1 void:函数"干完活不给东西"
场景:你写一个 ECharts 图表初始化函数,画完图就完事了,不需要返回什么。
/**
* 初始化仪表盘
* @param canvasId - canvas 元素的 id
* @returns 无返回值
*/
function initGauge(canvasId: string): void {
const chart = echarts.init(document.getElementById(canvasId));
chart.setOption({
series: [{
type: 'gauge',
data: [{ value: 36.5, name: '温度' }]
}]
});
// 没有 return,返回值类型就是 void
}
// 事件回调也常用 void
canvas.addEventListener('click', (e: MouseEvent): void => {
console.log('点击坐标:', e.offsetX, e.offsetY);
});
一句话记忆:函数只干活不交差 → void
常见用法:图表初始化、事件监听回调、日志打印、DOM 操作。
11.2 null / undefined:东西"没有"的两种状态
场景:从后端接口拿设备数据,“还没请求到"和"传感器没数据"是两种不同的"没有”。
// 设备数据接口
interface DeviceData {
temperature: number | null; // null = 传感器没数据
humidity: number | null; // null = 传感器没数据
}
// 当前设备状态
let currentDevice: DeviceData | null = null;
// null = 还没请求到数据(接口还没返回)
let errorMessage: string | undefined;
// undefined = 还没出错(声明了但没赋值)
// 有错后 = "连接超时"
// 请求成功后
currentDevice = { temperature: 36.5, humidity: 60 };
// 请求失败后
currentDevice = null;
errorMessage = '连接超时';
区分:
undefined= 声明了但没赋值(“还没给”)null= 主动设为空(“故意设为没有”)
常见用法:接口数据加载状态、可选配置项、清除引用。
11.3 any vs unknown:两个"什么都能放"的口袋
场景:你从 WebSocket 收到一段数据,格式不确定。
// ❌ any:关掉类型检查,随便用,运行时可能炸
function handleMessageBad(msg: any) {
console.log(msg.device.name); // 不报错,但如果 msg 没有 device 呢?
msg.forEach(); // 不报错,但如果 msg 不是数组呢?
}
// ✅ unknown:可以接收任何值,但用之前必须先检查类型
function handleMessageGood(msg: unknown) {
// msg.device.name // 报错!TS 不让你直接用
if (typeof msg === 'object' && msg !== null && 'device' in msg) {
const obj = msg as { device: { name: string } };
console.log(obj.device.name); // 检查后才用
}
}
实际选择:
调试代码、快速原型 → 暂时用
any处理外部数据(接口、WebSocket、localStorage)→ 用
unknown
一句话记忆:any 是"我不管了",unknown 是"先查再用"
常见用法:WebSocket 消息处理、localStorage 数据读取、第三方 API 返回值、JSON.parse 结果。
11.4 enum(枚举):固定几个选项的状态
场景:工业设备有固定几种运行状态,用数字不直观,用字符串容易拼错。
// 定义设备状态枚举
enum DeviceStatus {
Running = 'RUNNING',
Stopped = 'STOPPED',
Alarm = 'ALARM',
Offline = 'OFFLINE',
}
// 用在状态判断 —— 根据状态返回颜色
function getStatusColor(status: DeviceStatus): string {
switch (status) {
case DeviceStatus.Running: return '#2ed573'; // 绿
case DeviceStatus.Alarm: return '#ff4757'; // 红
case DeviceStatus.Stopped: return '#ffa502'; // 橙
case DeviceStatus.Offline: return '#8a8a99'; // 灰
}
}
// 用在数据绑定
const device = {
name: '泵机A',
status: DeviceStatus.Running // 语义清晰,不会拼错
};
// 用在 UI 渲染
if (device.status === DeviceStatus.Alarm) {
showAlarmDialog(); // 告警弹窗
}
什么时候用 enum:值的选项是固定的、有限的、需要语义化的。
什么时候不用:
只有两个选项 → 直接用
boolean简单字符串 → 用联合类型
'RUNNING' | 'STOPPED'(第3天会学)
常见用法:设备状态、订单状态、用户角色、图表类型、告警等级。
11.5 tuple(元组):固定长度+固定顺序的数组
场景:坐标点、CSV 数据行、React useState 返回值。
// 工业场景:设备坐标 [x, y]
const position: [number, number] = [120, 360];
// 大屏布局:[x, y, width, height]
const panelLayout: [number, number, number, number] = [0, 0, 1920, 360];
// CSV 数据行:[设备名, 温度, 状态]
const row: [string, number, string] = ['泵机A', 36.5, 'RUNNING'];
// RGB 颜色值
const alarmColor: [number, number, number] = [255, 71, 87];
// React 的 useState 就是返回元组
const [count, setCount] = useState<number>(0);
// 返回类型是 [number, (n: number) => void]
// 第一个位置是值,第二个位置是 setter 函数
和数组的区别:
number[]= 长度随意,全是 number[number, number]= 只能 2 个,都是 number,顺序固定
一句话记忆:元组是"位置有意义的固定长度数组"
常见用法:坐标点、颜色值、CSV 数据行、React Hooks 返回值、HTTP 响应 [状态码, 响应体]。
11.6 never:永远不可能出现的类型
场景:穷尽性检查——确保 switch 覆盖了所有情况,防止遗漏。
// 定义设备状态类型
type DeviceStatus = 'running' | 'stopped' | 'alarm' | 'offline';
// 根据状态渲染图标
function getStatusIcon(status: DeviceStatus): string {
switch (status) {
case 'running': return '✅';
case 'stopped': return '⏸️';
case 'alarm': return '🔴';
case 'offline': return '⚪';
default:
// 如果将来 DeviceStatus 新增了 'maintenance',
// 但这里忘了加 case,TS 会立刻报错!
const _check: never = status;
return _check;
}
}
为什么重要:以后你给 DeviceStatus 加了 'maintenance' 但忘了在 switch 里处理,TypeScript 会立刻报错提醒你。这就是 never 的价值——防止遗漏。
一句话记忆:never = “这行代码永远不该执行到”,用来做安全网
常见用法:switch 穷尽性检查、不可能到达的代码分支、抛出错误的工具函数。
11.7 综合实战:一个完整的设备监控模块
把以上所有类型串起来,写一个真实的设备监控模块:
export {};
// 设备状态枚举
enum DeviceStatus {
Running = 'RUNNING',
Stopped = 'STOPPED',
Alarm = 'ALARM',
Offline = 'OFFLINE',
}
// 设备数据接口(null 表示传感器无数据)
interface DeviceData {
temperature: number | null;
humidity: number | null;
pressure: number | null;
}
// 设备坐标(元组:固定 [x, y])
type Position = [number, number];
// 设备完整信息
interface Device {
id: string;
name: string;
status: DeviceStatus;
position: Position;
data: DeviceData | null; // null = 还没收到数据
}
// 初始化仪表盘(void:不返回值)
function initDashboard(canvasId: string): void {
console.log(`初始化仪表盘: ${canvasId}`);
}
// 处理 WebSocket 消息(unknown:外部数据先检查再用)
function handleWsMessage(msg: unknown): void {
if (typeof msg === 'object' && msg !== null && 'deviceId' in msg) {
const data = msg as { deviceId: string; temperature: number };
console.log(`设备 ${data.deviceId} 温度: ${data.temperature}`);
}
}
// 获取状态颜色(never:穷尽性检查)
function getStatusColor(status: DeviceStatus): string {
switch (status) {
case DeviceStatus.Running: return '#2ed573';
case DeviceStatus.Stopped: return '#ffa502';
case DeviceStatus.Alarm: return '#ff4757';
case DeviceStatus.Offline: return '#8a8a99';
default:
const _exhaustive: never = status;
return _exhaustive;
}
}
// 创建设备
const pump: Device = {
id: 'PUMP-001',
name: '泵机A',
status: DeviceStatus.Running,
position: [120, 360], // 元组
data: { // 有数据
temperature: 36.5,
humidity: 60,
pressure: null, // 压力传感器没数据
}
};
// 创建未连接的设备
const sensor: Device = {
id: 'SENSOR-002',
name: '温度传感器B',
status: DeviceStatus.Offline,
position: [240, 180],
data: null, // 还没收到数据
};
// 使用
initDashboard('gauge-canvas'); // void
console.log(getStatusColor(pump.status)); // #2ed573
handleWsMessage({ deviceId: 'PUMP-001', temperature: 37.2 }); // unknown
这段代码覆盖了:enum(状态)、tuple(坐标)、void(初始化函数)、null(无数据)、unknown(WebSocket 消息)、never(穷尽检查)。
11.8 学习优先级建议
不是所有类型都要现在吃透,分个优先级:
十二、自测挑战
把以下代码复制到 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 天内容。更多学习内容请关注专栏更新。