day01-welcome-to-typescript-world

作者:mario 发布时间: 2026-07-28 阅读量:39 评论数:0

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 + 类型系统

具体来说:

特性

JavaScript

TypeScript

类型系统

动态类型(运行时检查)

静态类型(编译时检查)

类型错误

运行时暴露

编译时暴露

IDE 支持

有限(基于推断)

强大(基于类型注解)

学习曲线

中等

生态

所有浏览器原生支持

需编译为 JS 才能运行

关系

TS 是 JS 的超集

TS 是 JS 的超集意味着:

  1. 所有合法的 JavaScript 代码都是合法的 TypeScript 代码

  2. TypeScript 额外增加了类型注解语法

  3. TypeScript 编译后会擦除所有类型信息,输出纯 JavaScript

// 这段 TS 代码
const name: string = "张三";
console.log(name);

// 编译后输出的 JS 代码(类型被擦除)
 const name = "张三";
 console.log(name);


三、环境准备:TypeScript Playground

无需安装任何东西,直接打开浏览器即可开始学习。

3.1 访问 Playground

打开 TypeScript Playground

3.2 界面说明

区域

说明

左侧

TypeScript 代码编辑器,实时输入代码

右侧 .JS 标签

编译后的 JavaScript 代码

右侧 .D.TS 标签

类型声明文件

右侧 Errors 标签

类型错误信息

左侧 TS Config

编译选项(建议勾选 strict

3.3 重要提示

如果你遇到以下报错:

Cannot redeclare block-scoped variable 'name'.

原因:TypeScript 默认把 Playground 中的代码当作"全局脚本"处理,而浏览器的全局作用域中已存在 window.name 属性,产生冲突。

解决方法:在代码最顶部加一行 export {};,告诉 TS 这是独立模块:

export {}; // 让 TS 把这段代码当作模块处理
let name: string = "张三"; // 不再报错


四、基础原始类型

TypeScript 最基础的类型是三种原始类型:stringnumberboolean

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

anyunknown 都表示"任意类型",但安全性截然不同。

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 对比

特性

any

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 模式下(建议开启),nullundefined 是独立的类型,不能随意赋给其他类型:

// 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 四种特殊类型对比

类型

含义

典型场景

void

没有返回值

函数不 return

null

值为空

显式表示"没有值"

undefined

未定义

变量声明但未赋值

never

永不出现

抛错、死循环、穷尽检查


九、类型推断

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 x = "hello"

string

let x = 42

number

let x = [1, 2, 3]

number[]

let x = [1, "hello"]

(string | number)[]

let x = null

any(非 strict 模式)

const x = "hello"

"hello"(字面量类型)

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 会沿着调用链一路传播,类型安全防线崩溃

最佳实践清单

  1. 始终开启 strict 模式:享受完整的类型保护

  2. 函数参数必须写类型注解:TS 无法推断参数类型

  3. 变量能推断就不写注解:减少冗余代码

  4. 优先用 unknown 而非 any:安全第一

  5. never 做穷尽性检查:防止 switch 漏分支

  6. 字符串枚举优于数字枚举:调试更友好

  7. 性能敏感处用 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      — 永不出现(抛错/死循环/穷尽检查)

速记口诀

类型

一句话记忆

string / number / boolean

三大原始类型,写法:变量: 类型

T[] / Tuple

数组灵活长度,元组固定结构

enum

给固定值起名字,const enum 零开销

any

自由但危险,类型检查的逃生舱

unknown

安全的 any,用前必须收窄

void

函数没有返回值

never

永不出现,穷尽检查的利器

学习成果检查

学完本文后,你应该能够:

  • [ ] 说出 TypeScript 所有基础类型及其使用场景

  • [ ] 区分数组和元组的适用场景

  • [ ] 解释 anyunknown 的区别

  • [ ] 理解 never 类型的三种使用场景

  • [ ] 知道什么时候写类型注解,什么时候靠推断

  • [ ] 在 Playground 中独立完成类型实验


推荐资源

资源

说明

TypeScript Playground

在线编码实验,零安装

TypeScript 官方 Handbook

官方文档,权威准确

type-challenges

类型体操刷题,从 easy 到 extreme

TypeHero

闯关式学习,游戏化体验

Execute Program

间隔记忆卡片,巩固记忆

《TypeScript 编程》Boris Cherny

系统学习 TS 的好书


下一步

本文是 TypeScript 深入 系列的第 1 天。接下来的学习路线:

  • 第 2 天:接口 vs 类型别名 — 两种定义对象形状的方式

  • 第 3 天:联合类型与交叉类型 — 类型组合的艺术

  • 第 4 天:类型守卫与类型断言 — 安全的类型收窄

  • 第 5 天:枚举与字面量类型 — 定海神针

  • 第 6 天:keyof / typeof / 索引访问 — 类型钥匙

  • 第 7 天:第一关 BOSS 战 — 综合实战


学编程就像蜗牛往上爬,慢一点没关系,关键是不停下来。

每天花 2 小时,28 天通关 TypeScript 深入。加油!


本文是「Web 前端工业可视化学习」路线的 TypeScript 阶段第 1 天内容。更多学习内容请关注专栏更新。

评论