Typescript 基础复习 - 速通版
- 2026-10-10 04:49:01
TS 是 JS 的超集,在 JS 之上加了一层静态类型系统,由编译器(tsc)在编译期做类型检查,最终擦除类型、编译成纯 JS 运行。
TS 并不改变 JS 运行时行为——所有类型在编译后都被擦除,它是给开发者和编译器看的'文档 + 校验器',不影响线上性能。
// 原始类型let name: string = "Tom";let age: number = 18;let isAdmin: boolean = true;// 数组:两种写法let list1: number[] = [1, 2, 3];let list2: Array<number> = [1, 2, 3];// 元组 Tuple:固定长度、各位置类型已知let pair: [string, number] = ["id", 42];// 空值类let u: undefined = undefined;let n: null = null;function log(): void { console.log("hi"); }// 任意类型(尽量少用)let anything: any = 1;anything = "now string"; // 不报错——绕过了类型检查
number 不区分 int/float(和 JS 一样都是双精度);string 包含模板字符串;没有独立的 integer 类型。
类型注解(Annotation)
// 开发者显式写明类型function greet(name: string): string {return `Hello, ${name}`;}
类型推断(Inference)
// 编译器自动推导,不用写let x = 1; // 推断为 numberlet arr = [1, 2]; // 推断为 number[]// 推断不出时必须显式标注:let v; // 推断为 any(strict 下 noImplicitAny 会报错)function add(a: number, b: number) { return a + b; }// 返回值也能推断,但公共 API 建议显式写 return 类型
变量初始化时能推断 → 不写
函数参数、函数返回值、公共接口/导出类型 → 显式写
优先 interface,需要联合/交叉/元组/字面量时用 type
interface B extends A | A & B | |
A | B、A & B | ||
// interface 可声明合并(同名多次声明会合并)——常用于扩展第三方库interface Window { myPlugin: Plugin }interface Window { other: string } // 合并,不报错// type 能做 interface 做不了的事type ID = string | number; // 联合type Point = { x: number } & { y: number }; // 交叉type Gender = "male" | "female"; // 字面量联合type Pair = [string, number]; // 元组
两者都能描述对象;interface 开放、可合并、适合公共契约;type 表达力更强(联合/交叉/元组/字面量),但不能重复声明。日常对象形状优先 interface。
联合类型 A | B:取其一
function printId(id: string | number) {console.log(id);}
交叉类型 A & B:同时具备所有成员
type WithName = { name: string };type WithAge = { age: number };type Person = WithName & WithAge;// { name: string; age: number }
字面量类型:把"值"本身当类型
let dir: "up" | "down" | "left" | "right";dir = "up"; // OK// dir = "middle"; // 报错——只能取这四个字面量之一const n = 42; // const 推断为字面量类型 42let m = 42; // let 推断为 number(可变)
联合是 "或"(OR,缩小取值范围),交叉是 "且"(AND,合并属性)。字面量类型常用于开关、状态、方向。
// 完整标注:(参数) => 返回值type Add = (a: number, b: number) => number;const add: Add = (a, b) => a + b;// 可选参数 ? / 默认值 / 剩余参数function fn(a: string, b?: number, c: boolean = false, ...rest: string[]) {}// 函数重载:同一个函数根据入参返回不同类型// 【1】重载签名(只写类型,没有函数体):多个,供 TS 做类型匹配function format(input: string): string;function format(input: number): string[];// 【2】实现签名(带函数体):只能有 1 个,必须兼容上面所有重载签名function format(input: string | number): string | string[] {// 实现签名(对外不可见)return typeof input === "string" ? input : [`#${input}`];}
重载的实现签名对调用者不可见;调用方只看上面的重载签名。
用联合类型 + 类型收窄常常能代替重载,但`重载的表达能力更强`(对外提供清晰的类型定义)
7. 泛型 Generics
类型也能"参数化" —— 复用、又不丢失类型信息
// 不用泛型:丢失类型function identity1(arg: any): any { return arg; }// 用泛型 T:传入什么类型,返回就是什么类型function identity<T>(arg: T): T { return arg; }const s = identity<string>("hi"); // s: string// 泛型约束 extends:要求 T 至少有某些结构function len<T extends { length: number }>(x: T): number {return x.length;}// 常见用法:泛型函数 / 泛型接口 / 泛型工具类型interface ApiResponse<T> {code: number;data: T;msg: string;}type UserResp = ApiResponse<{ id: number; name: string }>;
「泛型和 any 区别?」—— any 抛弃类型检查;泛型保留了输入与输出的类型关联,调用方拿到的是精确类型。
// 数字枚举:默认从 0 自增,可手动指定enum Direction { Up = 1, Down, Left, Right }// Up=1, Down=2, Left=3, Right=4// 字符串枚举:可读性好,序列化友好enum OrderStatus {PENDING = "PENDING",PAID = "PAID",SHIPPED = "SHIPPED",}// 常量枚举:编译时内联,不生成对象,体积更小const enum Color { Red, Green, Blue }// 相当于:const enum Color {Red, // 0 (默认从0开始自增)Green, // 1Blue // 2}// 也可以手动赋值:const enum Color {Red = 10,Green, // 11Blue = 20}
社区越来越倾向用 联合字面量类型 + as const 代替 enum,
因为 enum 会生成运行时代码、且存在双向映射(数字枚举可由值反查键)带来的意外。
enum Color { Red = 0, Green = 1 }// 编译后 JS 对象是双向的:// Color[0] = "Red";// Color["Red"] = 0;const val = Color[0]; // ✅ 运行时可以拿到 "Red"
新项目可优先:
const Colors = ['red','green'] as const; // readonly ["red", "green"]type Color = typeof Colors[number]; // "red" | "green"
1. `typeof Colors`:拿到上面常量的类型 → `readonly ["red", "green"]`
2. `[number]`:索引访问类型
数组 / 元组的索引类型是 `number`;
`T[number]` 的含义:取出类型 T 里 所有元素的类型,合并成联合类型
type T = readonly ["red", "green"];type Color = T[number];// 等价于 "red" | "green"
9. 类型断言 / 类型守卫 / 类型收窄
类型断言(Type Assertion) as:你比编译器更确定类型
const el = document.querySelector("#name") as HTMLInputElement;el.value; // 断言后能访问 value// 另一种写法(在 .tsx 里必须用 as,避免 <> 被当成 JSX)const s = <string>input;// 相当于:const s = input as string
断言只是"哄骗"编译器,不做真实转换。x as number 不会把字符串变成数字,运行时类型错了照样炸。不确定时用类型守卫。
类型守卫 / 收窄:让编译器在分支里自动缩窄类型
// typeof 守卫function toUpper(x: string | number) {if (typeof x === "string") {x.toUpperCase(); // 这里 x 被收窄为 string}}// instanceof / in 守卫if (x instanceof Date) x.getFullYear();if ("id" in obj) obj.id;// 自定义类型谓词 (value is Type)function isString(v: unknown): v is string {return typeof v === "string";}
1. 内置类型守卫:`typeof` / `instanceof` / `in`,TS 原生识别
2. 自定义类型守卫:自己写函数,返回 `value is Type`时为 true
10. 内置工具类型 Utility Types
Partial<T> | { [K in keyof T]?: T[K] } | |
Required<T> | ?(-?) | |
Readonly<T> | readonly | |
Pick<T, K> | { [P in K]: T[P] } | |
Omit<T, K> | Pick<T, Exclude<keyof T, K>> | |
Record<K, V> | { [P in K]: V } | |
Exclude<U, Ex> | U extends Ex ? never : U | |
Extract<U, U2> | U extends U2 ? U : never | |
NonNullable<T> | T extends null | undefined ? never : T | |
ReturnType<F> | F extends (...args) => infer R ? R : never |
interface User {id: number;name: string;email: string}type UserUpdate = Partial<User>; // 全可选type UserName = Pick<User, "id" | "name">; // 只留 id, nametype UserNoMail = Omit<User, "email">; // 去掉 emailtype UserMap = Record<number, User>; // { [id]: User }
keyof T(取所有键的联合)、in(遍历)、
infer R(类型推导,在条件类型里"抽出"某个类型变量)
`infer` 是在条件类型 `T extends ... ? A : B` 的 `extends` 分支里,用来「推导、提取」一个类型变量的关键字。
infer 只能用在 条件类型的 true 分支 里,不能单独使用
1) 提取函数返回类型
// 内置 ReturnType 实现,提取函数返回类型type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
T extends (...args: any[]) => infer R // 判断 T 是否是函数类型type MyAwaited<T> = T extends Promise<infer V> ? V : T;type P = Promise<number>;// 拿到 Promise 包裹的类型type Res = MyAwaited<P>; // number// 不是 promisetype Res2 = MyAwaited<string>; // string
type MyParameters<T> = T extends (...args: infer Args) => any ? Args : never;type Fn = (a: number, b: string) => void;type Params = MyParameters<Fn>; // [number, string]
type GetArrayItem<T> = T extends (infer Item)[] ? Item : T;type T1 = GetArrayItem<string[]>; // stringtype T2 = GetArrayItem<number[]>; // numbertype T3 = GetArrayItem<boolean>; // boolean
let u: unknown = 123;u.length; // ❌ 报错!不能直接访问属性let s: string = u; // ❌ 不能直接赋值// ✅ 必须收窄后才能使用if (typeof u === "string") {let s: string = u;console.log(u.length);}
// 场景1:抛出异常,永远没有返回值 → neverfunction crash(): never {throw new Error("boom");}// 场景2:死循环,永远不会返回function loop(): never {while(true) {}}
never 用于穷尽性检查(exhaustive check):保证联合类型所有分支都被处理。如果漏写分支,TS 就会报类型错误。
type Shape =| { type: "circle"; r: number }| { type: "square"; size: number }| { type: "rect"; w: number; h: number };function getArea(shape: Shape) {switch (shape.type) {case "circle":return Math.PI * shape.r **2;case "square":return shape.size **2;case "rect":return shape.w * shape.h;default:// ✅ 穷尽检查const _exhaustiveCheck: never = shape;throw new Error(`unknown shape: ${JSON.stringify(_exhaustiveCheck)}`);}}
void 函数 没有 有效返回值
// 不写return,返回voidfunction log(): void {console.log("hi");}// 可以return undefined,合法function log2(): void {return undefined;}// ❌ 不能return其他值function log3(): void {return 123;}
小结:any 是关掉检查; unknown 是开着检查但要求你先收窄再用; never 表示'不可能到达',用于穷尽判断; void 表示函数没返回值。
12. 结构类型系统(鸭子类型)
interfacePoint{ x: number; y: number }functiondraw(p: Point) {console.log(p.x, p.y);}const obj = { x: 1, y: 2, z: 3 }; // 多了个 z,仍然兼容draw(obj); // OK:形状足够// 但对象字面量直接传会触发"多余属性检查"(excess property check)draw({ x:1, y:2, z:3 }); // 报错:对象字面量只能指定已知属性
结构类型 = "如果它走起来像鸭子、叫起来像鸭子,那它就是鸭子"。这也是 TS 能和普通 JS 对象无缝协作的原因。
项目里怎么配的 strict ?
strict | |
noImplicitAny | |
strictNullChecks | nullundefined 不能赋给其他类型,必须显式收窄 |
strictFunctionTypes | |
target | |
module | |
outDir / rootDir | |
sourceMap | |
declaration | |
esModuleInterop | import React from 'react' 这种默认导入写法 |
skipLibCheck |
strict = true 会自动开启下面所有选项:
{"compilerOptions": {"strict": true,// 下面这些全部自动启用"noImplicitAny": true,"noImplicitThis": true,"strictNullChecks": true,"strictFunctionTypes": true,"strictPropertyInitialization": true,"strictBindCallApply": true,"useUnknownInCatchVariables": true,"alwaysStrict": true}}
⭐ strictNullChecks
默认关闭时:`string` 类型变量 可以赋值 null /undefined。
开启后:`string` 不包含 null、undefined,必须显式写联合类型 `string | null`。
解决大量空值导致的运行时报错,是 strict 里价值最高的一项之一。
后续再介绍 zod 相关的结合 AI 结构化输出 等内容。