TypeScript 入门:给 JS 穿上类型
理解类型注解、interface/type、联合类型,知道为什么类型能在编译期抓错
- 理解 TypeScript 相比 JS 多解决了什么问题
- 掌握类型注解、interface/type 基本写法
- 理解联合类型、数组与对象类型
- 能读懂 .tsx 文件里常见的类型标注
TypeScript 很像你在 Python 里写的 type hints(def add(a: int, b: int) -> int),但区别是:Python 的注解默认不检查,要靠运行时或 mypy;TypeScript 是在代码跑起来之前(编译期)就直接报错。相当于把「函数参数类型」从注释变成了硬性合同。
为什么要类型?
JavaScript 是动态类型:变量今天存数字,明天存字符串都可以。好处是灵活,坏处是很多错误要等到用户点了按钮、甚至线上才暴露。TypeScript 给 JS 加上一层静态类型检查,在你敲代码的时候就告诉你「这里传错类型了」。
// TypeScript 类型注解:变量名后面加 : 类型
let age: number = 25;
let name: string = "小明";
let isLogin: boolean = true;
// 函数参数和返回值也能标注
function add(a: number, b: number): number {
return a + b;
}
add(1, 2); // OK
add(1, "2"); // 编译期就报错:Argument of type 'string' is not assignable to parameter of type 'number'JS 里 add(1, '2') 会默默得到 '12'(字符串拼接),错误藏着;TS 在你保存文件那一刻就画红线。类型是写给人和编辑器看的,编译后会被擦除,浏览器跑的还是普通 JS。
这里有个新手最容易误解的点:类型在编译后被完全擦除,意味着它不参与运行时。也就是说,TS 能挡住你「自己写代码时传错类型」,但挡不住「外部输入的类型根本和你想的不一样」。一个典型翻车现场:你写 const user = JSON.parse(res) as User,TS 高高兴兴放行——可这个 user 是从网络、localStorage、第三方 SDK 来的,编译期根本看不到它的真实形状,万一后端少返回了一个字段,运行时读 user.name 就是 undefined,页面照样白屏。
类型检查的边界是「你自己写的、编译期看得见的代码」。凡是边界外的数据(HTTP 响应、URL 参数、本地缓存、用户上传),TS 都不会替你校验。生产里这类数据要靠运行时校验库(如 zod,运行时再验一遍形状),而不是一个 as User 就完事。记住一句话:as 是你在说服编译器,不是编译器在保证数据真的对。
interface 与 type:描述对象形状
前端最常见的数据是对象(用户、课程、卡片)。用 interface 或 type 描述它有哪些字段、各是什么类型。
// interface:描述一个对象长什么样
interface User {
id: number;
name: string;
email?: string; // 加 ? 表示可选字段
}
// type:更通用的别名方式
type Post = {
id: number;
title: string;
author: User; // 嵌套引用
};
const me: User = { id: 1, name: "小明" }; // 少写 email 不报错
const bad: User = { id: "1", name: 123 }; // 报错:id 要 number联合类型、数组与对象数组
// 联合类型:取值可以是这几种之一
type Status = "pending" | "done" | "error";
let s: Status = "done";
// 数组:元素类型后加 []
let nums: number[] = [1, 2, 3];
let users: User[] = [{ id: 1, name: "小明" }];
// 函数接收一个 User 数组,返回用户名数组
function namesOf(users: User[]): string[] {
return users.map(u => u.name);
}类型断言 vs 类型守卫:别用 any 糊弄
写着写着类型报错了,新手最常见的反应是两件事:到处写 any,或者到处用 as 断言强转。any 是「关闭类型检查」——一旦写成 any,TS 就对这个值彻底失明,你在它身上点任何方法都不报错,等于退回到 JS。类型断言 as 则是「我比编译器更懂,听我的」,它不做任何运行时检查,只是让编译器闭嘴。两者都能让红线消失,但错误一个都没少,只是被藏起来了。
// 反例:用 any 和 as 把问题压下去,编译过了,运行时炸
const data: any = fetchSomething();
const name = (data as User).name; // 编译器信了,但 data 真有 name 吗?
// 正例:类型守卫——运行时真的判断,判断完 TS 自动收窄类型
function isUser(x: unknown): x is User {
return typeof x === "object" && x !== null
&& typeof (x as User).id === "number"
&& typeof (x as User).name === "string";
}
if (isUser(raw)) {
raw.name; // 这里 TS 知道 raw 一定是 User,放心用
}as 只在「你确实比编译器多知道一点、且这个断言安全」时用,比如把 HTMLElement 收窄成 HTMLInputElement。unknown 比 any 安全:unknown 强制你先用守卫收窄才能用,any 则放任你乱用。团队 code review 里,裸 any 和无依据的 as 通常是要被打回的。
和 Python type hints 到底差在哪
你在 Python 里写的 def add(a: int, b: int) -> int,看起来和 TS 一模一样,但约束力天差地别。Python 的注解默认只是「注释」:解释器根本不看它,你传 add(1, '2') 照样跑,只有单独跑 mypy/pyright 这类检查器才会报错。TS 则是把检查器默认打开、集成进编辑器,保存即查。
Python type hints:写给人看 + 可选工具检查,运行时不拦;TypeScript:写给编译器看,默认强制检查,跑之前拦。想让 Python 也变强,可以用 mypy 配 pre-commit,但那是额外工序;TS 把这道工序做成了默认。语法细节与版本以官方最新文档为准。
下一节写 React 组件时,props 都要标类型:function Card({ title, size }: { title: string; size?: number })。这就是 interface/type 的实战。本项目大量用 .tsx,学会读类型标注,你才能看懂组件到底接收什么参数。版本与语法细节以官方最新 TypeScript 文档为准。
动手:用 Python 模拟类型检查器
下面这段代码模拟 TS 编译期做的事:在函数入口检查参数类型,传错就立刻抛错,而不是带着错误继续跑。
相比纯 JavaScript,TypeScript 最大的好处是什么?
资深工程师加餐
底层原理 · 大厂视角 · 工程经验,点卡片展开
穿透是反复查「根本不存在」的数据,用布隆过滤器或缓存空值挡;击穿是某个热点 key 过期瞬间大量请求压向数据库,用互斥重建或逻辑过期;雪崩是大批 key 同时过期,给 TTL 加随机抖动。缓存与库的一致性常用「先更新数据库、再删除缓存」,并靠重试/订阅补偿。
挑战任务
写一个带类型断言的函数
TypeScript 在编译期检查参数类型。用 Python 模拟这个过程:实现 require_str(value),当 value 不是字符串时抛 TypeError("需要字符串"),是字符串就原样返回。
课后作业
给不可信数据写类型守卫
后端接口偶尔返回畸形 JSON(比如 id 错传成了字符串)。实现 is_user(obj):只有当 obj 是字典、且 id 是整数、name 是字符串时才返回 True,否则 False。这就是运行时的类型守卫,对应 TS 里的 x is User。