Skip to content

第 09 章 · TypeScript 基础类型

一句话开篇:TypeScript 给 JavaScript 装上了"安检门"——在你打包发车之前,提前帮你检查好每一件行李是不是装错了箱子。


0. 生活类比:从"自助餐"到"分餐制"

想象你开了一家餐厅。

JavaScript 就像自助餐:客人随便拿盘子,你也不知道他端走的是不是甜品(说不定他把咸鱼放进了草莓酱里)。等到客人吐了,你才知道出问题了——这就是运行时报错

TypeScript 就像分餐制:每一道菜都有自己的盘子、自己的位置、自己的标签:

  • 这盘是牛排steak: Steak),不能放进甜品区
  • 这碗是soup: Soup),不能用筷子夹
  • 这杯是红酒或白酒wine: Red | White),二选一

服务员(TypeScript 编译器)在上菜之前,就把所有错乱拦在厨房——客人吃到嘴里之前,错误就已经被发现了。

一句话:JS 是"先跑起来再说",TS 是"先检查再跑"


1. 概念是什么

TypeScript(TS) 是微软在 2012 年推出的开源编程语言,它是 JavaScript 的超集(Superset)

        ┌──────────────────────────────────┐
        │           TypeScript             │
        │  ┌──────────────────────────┐    │
        │  │       JavaScript         │    │
        │  │  (所有 JS 都是合法 TS)   │    │
        │  └──────────────────────────┘    │
        │   + 静态类型系统                 │
        │   + 接口、泛型、枚举              │
        │   + 编译时检查                    │
        └──────────────────────────────────┘
                    │ tsc 编译

        ┌──────────────────────────────────┐
        │           JavaScript             │
        │     (浏览器/Node 真正执行)      │
        └──────────────────────────────────┘

核心特性:

  • 静态类型:变量、参数、返回值都可以声明类型
  • 编译时检查:写错类型在 IDE 里就标红,不用等到上线
  • 类型擦除:编译后产物是纯 JS,运行时没有任何额外开销
  • 渐进式:可以一点一点加类型,新老代码无缝共存

2. 为什么需要 TS

真实业务场景

假设你正在写一个电商订单的函数:

js
// JavaScript 版本
function calcPrice(order) {
  return order.price * order.count + order.tax;
}

calcPrice({ price: 10, count: "2", tax: 1 }); // "10221"  哦豁,字符串拼接了
calcPrice({ price: 10, qty: 2, tax: 1 });     // NaN     字段写错了
calcPrice(null);                              // 💥 TypeError

这三种 bug 全部要等到用户下单的时候才发现——客服电话已经被打爆了。

ts
// TypeScript 版本
interface Order {
  price: number;
  count: number;
  tax: number;
}

function calcPrice(order: Order): number {
  return order.price * order.count + order.tax;
}

calcPrice({ price: 10, count: "2", tax: 1 }); // ❌ 编译报错
calcPrice({ price: 10, qty: 2, tax: 1 });     // ❌ 编译报错(缺 count)
calcPrice(null);                              // ❌ 编译报错

写下错误代码的那一刻,编辑器就红线了——根本没机会上线。

JS vs TS 一表对比

维度JavaScriptTypeScript
类型系统动态(运行时)静态(编译时)
报错时机用户触发后写代码时
IDE 体验自动补全靠猜精准提示、跳转、重构
重构成本改一个改全炸编译器先帮你找出所有连带处
学习曲线中(多学一套类型语法)
运行时开销无(编译后就是 JS)
团队协作看注释/猜接口类型即文档
适合小脚本、原型中大型项目、长期维护

3. 怎么用(最小示例)

一行入门

ts
const name: string = "Alice";
const age: number = 18;
const isVip: boolean = true;

function greet(user: string): string {
  return `Hello, ${user}!`;
}

console.log(greet(name));

在浏览器中尝试

最简单的方式是打开 TypeScript Playground,左边写 TS、右边自动看到编译后的 JS。

本地编译

bash
npm i -g typescript
tsc hello.ts          # 生成 hello.js
node hello.js

4. 进阶要点

4.1 基本类型一览

类型含义示例
string字符串"abc"
number数字(整数/浮点都算)423.14
boolean真假true / false
null空值null
undefined未定义undefined
symbol唯一标识Symbol("id")
bigint大整数9007199254740993n
any"我放弃了",任何值都行let x: any = 1; x = "abc";
unknown"暂时不知道",更安全的 any必须类型守卫后才能用
void函数没有返回值function log(): void {}
never永远不会返回(抛异常/死循环)function fail(): never { throw ... }

any vs unknown vs never 的关键区别

ts
let a: any = 1;
a.foo.bar.baz();     // ✅ 编译通过(运行时直接炸)

let u: unknown = 1;
u.toFixed();         // ❌ 编译报错:必须先收窄类型
if (typeof u === "number") {
  u.toFixed();       // ✅ 这里 u 被收窄为 number
}

function loop(): never {
  while (true) {}    // 永远不返回
}

类比:

  • any信任客户,他说啥我都信
  • unknown陌生人来店,得先验明正身才能服务
  • never不可能发生,比如"下雪天太阳从西边出"

4.2 数组与元组

ts
// 数组:所有元素同类型
const nums: number[] = [1, 2, 3];
const strs: Array<string> = ["a", "b"];   // 等价写法

// 元组:固定长度、每个位置类型不同
const point: [number, number] = [10, 20];
const user: [string, number, boolean] = ["Alice", 18, true];

// 越界 / 类型错都会报错
point[2];                // ❌ 长度只有 2
user[1] = "abc";         // ❌ 第 2 个必须是 number

类比:

  • 数组 = "一筐苹果"(同种水果,数量随意)
  • 元组 = "宜家收纳盒"(格子固定,第 1 格放螺丝、第 2 格放螺母)

4.3 对象类型与字面量类型

ts
const user: { name: string; age: number } = { name: "A", age: 18 };

// 字面量类型:值本身就是类型
let dir: "up" | "down" | "left" | "right";
dir = "up";    // ✅
dir = "north"; // ❌

字面量类型 + 联合 = TS 版的枚举,强大且零运行时开销。

4.4 接口(interface) vs 类型别名(type)

维度interfacetype
描述对象✅ 强项✅ 可以
描述联合/交叉/原始类型
同名合并(声明合并)✅ 自动合并❌ 报错
继承extends& 交叉
推荐场景公开 API、类的契约复杂组合、工具类型
ts
// interface
interface User {
  name: string;
}
interface User {
  age: number;       // 同名自动合并,最终 User = { name; age }
}

// type
type ID = string | number;             // interface 做不到
type Admin = User & { role: "admin" }; // 交叉

// 经验法则
// 描述"对象/类的形状"     → 优先 interface
// 描述"联合/交叉/工具类型" → 用 type

4.5 联合 | 与交叉 &

ts
type StringOrNumber = string | number;     // 联合:是其中一个
type FullUser = User & Admin;              // 交叉:两者都是

function format(x: string | number) {
  // 这里 x 还不能直接调用 .toUpperCase(),需要类型守卫
  if (typeof x === "string") return x.toUpperCase();
  return x.toFixed(2);
}

类比:

  • 联合 | = "套餐 A 或 套餐 B"(二选一)
  • 交叉 & = "VIP 卡 + 学生证"(两个身份合一)

4.6 函数类型

ts
// 完整签名
function add(a: number, b: number): number {
  return a + b;
}

// 可选参数 ?
function greet(name: string, title?: string) {
  return `${title ?? ""} ${name}`;
}

// 默认参数
function pow(x: number, n: number = 2) {
  return x ** n;
}

// 剩余参数
function sum(...nums: number[]): number {
  return nums.reduce((a, b) => a + b, 0);
}

// 函数类型别名
type Comparator<T> = (a: T, b: T) => number;
const byAge: Comparator<{ age: number }> = (a, b) => a.age - b.age;

4.7 类型断言(as)

"我比编译器更清楚这是什么"——用之前请三思。

ts
const el = document.getElementById("app") as HTMLDivElement;
el.innerText = "Hi";

// 双重断言(仅在迫不得已时使用)
const x = "123" as unknown as number;

类比:类型断言 = 跟安检员说"放心,这真的是水"——错了,自己负责。

4.8 类型守卫(Type Guard)

让 TS 在分支里"自动收窄"类型。

ts
// 1. typeof 守卫(仅原始类型)
function pad(x: string | number) {
  if (typeof x === "string") {
    return x.padStart(3, "0"); // x 是 string
  }
  return x.toFixed(2);          // x 是 number
}

// 2. instanceof 守卫(仅类)
function logErr(e: Error | string) {
  if (e instanceof Error) console.log(e.stack);
  else console.log(e);
}

// 3. in 守卫(对象属性)
type Cat = { meow: () => void };
type Dog = { bark: () => void };
function speak(p: Cat | Dog) {
  if ("meow" in p) p.meow();
  else p.bark();
}

// 4. 自定义守卫(is 关键字)
function isCat(p: Cat | Dog): p is Cat {
  return (p as Cat).meow !== undefined;
}

ASCII 图——类型收窄过程:

输入:x: string | number

   ┌────────┴─────────┐
   │ typeof x === "string"
   │        │         │
   ▼ Yes    │         ▼ No
 x: string  │       x: number
   │        │         │
   .padStart│        .toFixed

5. 实战案例

详见 examples/

文件内容
basic-types.ts11 种基础类型完整示例
interface-vs-type.ts接口 vs 类型别名 5 大对比
union-intersection.ts联合/交叉的真实业务用法
type-guard.ts4 种类型守卫的写法

6. ⚠️ 易踩的坑

  1. 滥用 any:写起来爽,等于关掉了 TS——团队里应该禁用 any,改用 unknown + 守卫。
  2. nullundefined 不开严格模式:建议在 tsconfig.json 打开 "strict": true,否则 string 也可能是 null
  3. 类型断言当万能膏药as Foo 不会做任何运行时检查,断言错了照样炸。
  4. 数组类型 Array<T>T[] 混用:本质等价,项目内统一一种风格即可。
  5. {} 不是"空对象":它表示"任何非 null/undefined 的值",连 1"abc" 都算合法。空对象应写 Record<string, never>{ [k: string]: never }
  6. Function 类型:所有函数都符合,但不知道参数和返回值,应改用具体签名 (x: number) => string
  7. enum 慎用:会生成额外运行时代码,且行为有歧义(数字枚举可双向映射),新代码推荐用 as const + 联合字面量替代。
ts
// ❌ 老派
enum Status { Open, Closed }

// ✅ 现代
const Status = { Open: "OPEN", Closed: "CLOSED" } as const;
type Status = typeof Status[keyof typeof Status];

7. 一句话总结

TypeScript 不是"另一种语言",而是给 JavaScript 加了一层"翻译前的安检"——多花 10% 时间写类型,少花 90% 时间排错。


8. 延伸阅读