TypeScript 从入门到实战:写给前端与 Node 工程师的高效指南
为什么选择 TypeScript
- 类型安全: 把错误前移到开发/编译阶段,显著降低线上事故。
- 更强 IDE 体验: 自动补全、重构、自文档化的 API。
- 大中型项目利器: 可维护性、可演进性、跨团队协作成本更低。
- 生态完善: 主流框架/库(React、Vue、Node、Nest)均良好支持。
一、核心概念与快速上手
基本类型与别名
type UserId = string & { readonly brand: unique symbol }; // 品牌化类型(Nominal)
interface User {
id: UserId;
name: string;
age?: number; // 可选属性
tags: string[];
}
- type vs interface
- interface 支持声明合并,适合描述对象结构和公共 API
- type 更灵活(联合、交叉、条件类型、映射类型)
函数与 this/重载
function greet(name: string): string {
return `Hello, ${name}`;
}
interface Formatter {
(input: string): string;
locale?: string;
}
function len(x: string): number;
function len(x: any[]): number;
function len(x: string | any[]) { return x.length; }
泛型与约束
function identity<T>(value: T): T { return value; }
interface ApiResponse<TData, TError = never> {
ok: boolean;
data?: TData;
error?: TError;
}
function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
const r = {} as Pick<T, K>;
keys.forEach(k => { r[k] = obj[k]; });
return r;
}
控制流收窄(Narrowing)
function print(value: unknown) {
if (typeof value === 'string') {
console.log(value.toUpperCase());
} else if (Array.isArray(value)) {
value.forEach(v => console.log(v));
}
}
联合类型与判别式联合
type LoadState =
| { type: 'idle' }
| { type: 'loading' }
| { type: 'success'; data: string }
| { type: 'error'; error: Error };
function handle(state: LoadState) {
switch (state.type) {
case 'success': return state.data;
case 'error': return state.error.message;
default: return null;
}
}
二、进阶类型:写出“更聪明”的类型
条件类型与推断
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;
type ElementType<T> = T extends (infer U)[] ? U : T;
映射类型与修饰符
type ReadonlyDeep<T> = {
readonly [K in keyof T]: T[K] extends object ? ReadonlyDeep<T[K]> : T[K]
};
type Mutable<T> = { -readonly [K in keyof T]: T[K] };
type RequiredBy<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
工具类型(常用)
Partial<T>、Required<T>、Readonly<T>、Pick<T, K>、Record<K, T>Omit<T, K>、NonNullable<T>、ReturnType<F>、Parameters<F>ThisType<T>(仅影响上下文类型)
三、工程化与 tsconfig 要点
常用配置
{
"compilerOptions": {
"target": "ES2020",
"module": "nodenext",
"strict": true,
"moduleResolution": "nodenext",
"esModuleInterop": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"baseUrl": ".",
"paths": { "@/*": ["src/*"] },
"rootDir": "src",
"outDir": "dist",
"sourceMap": true
},
"include": ["src"]
}
- strict: 强烈建议开启(更强的类型保护)
- module/moduleResolution: Node ESM/CJS 场景建议
nodenext - lib: 浏览器项目通常包含
dom;Node 项目可去掉dom避免冲突 - paths/baseUrl: 配合打包器/运行时解析器使用,避免相对路径地狱
监听与增量
npx tsc --init生成配置npx tsc --watch持续编译- 多包仓库用 Project References 提升构建效率
四、与 Node.js、打包器的协作
模块系统
- CJS:
require/module.exports - ESM:
import/export("type": "module"或.mjs) - Node 18+ 推荐 ESM,tsconfig 选
nodenext
运行与调试
- 纯 TS:
tsc编译后运行node dist/index.js - 开发期:
ts-node或打包器(esbuild/swc/vite)+ 热重载 - 调试:
node --inspect、VSCode launch 配置
打包器注意点
- 仅转译模式(esbuild/swc)可能跳过类型检查:搭配
tsc --noEmit做类型检查 - 路径别名需在打包器侧同步配置(如 Vite 的
resolve.alias)
五、常见坑与最佳实践
1) 类型擦除与运行时零开销
- TS 类型只在编译期存在,发射的 JS 不带类型检查
- 若需要运行时校验,配合
zod/io-ts等库
2) 全局名冲突(DOM 的 Node)
- 浏览器项目自动引入
dom声明,其中有全局Node - 避免将自定义类命名为
Node、Event、Error等 - 或:让文件成为模块(任意
export),或在lib中去掉dom
3) any/unknown/never
- 优先
unknown替代any,强制显式收窄 never表示“不可能到达”,常用于穷尽检查
function exhaustive(_: never): never { throw new Error('unreachable'); }
4) 类型泄漏与边界
- 库导出的类型要稳定:导出
type而非“原始内部结构” - API 边界用 DTO/Schema 固定合同
5) 仅类型导入与消除副作用
import type { Config } from './types'; // 不会引入运行时代码
六、实战范式与模式
结果类型(错误不抛异常)
type Ok<T> = { ok: true; value: T };
type Err<E> = { ok: false; error: E };
type Result<T, E = Error> = Ok<T> | Err<E>;
async function safeFetchJson<T>(url: string): Promise<Result<T>> {
try {
const res = await fetch(url);
if (!res.ok) return { ok: false, error: new Error(res.statusText) };
return { ok: true, value: await res.json() as T };
} catch (e) {
return { ok: false, error: e as Error };
}
}
API 响应的“数据契约”与校验
import { z } from 'zod';
const UserDto = z.object({
id: z.string(),
name: z.string(),
});
type UserDto = z.infer<typeof UserDto>;
深度只读/可变切换
type DeepReadonly<T> = { readonly [K in keyof T]: DeepReadonly<T[K]> };
type DeepMutable<T> = { -readonly [K in keyof T]: DeepMutable<T[K]> };
七、测试与类型测试
单元测试
- Jest/Vitest + ts-jest/内置 TS 支持
- Node ESM 时优先 Vitest
类型级测试(高级)
- dtslint、expect-type、tsd:确保导出类型契约不被破坏
八、迷你清单(Cheat Sheet)
- 启动:
npm i -D typescript && npx tsc --init - 编译:
npx tsc -w(需tsconfig.json) - 强类型:开启
strict - 避冲突:避免全局名(如
Node),或移除dom - 仅转译:打包器转译 +
tsc --noEmit做类型检查 - 别名:同步配置 tsconfig 与打包器
- 生产:导出
.d.ts(declaration: true)保证类型体验
结语
TypeScript 的价值在于“开发期的确定性”和“演进中的安全网”。理解类型擦除、控制流收窄、泛型与工程化配置,配合合理的模式(Result、Schema 校验、类型边界),就能在浏览器与 Node 场景下写出更稳健、可维护的代码。如果你要落地到团队,优先从严格模式、规范的 tsconfig、稳固的类型契约和 CI 中的类型检查做起。