为什么 TS
- 静态类型 = 编译期发现错(少跑、少崩)
- IDE 智能提示(自动补全 + 跳转)
- 重构友好
- 团队代码"自文档化"
2026 新项目几乎都用 TS——Node 也不例外。
装 + 配
npm install -D typescript @types/node
npx tsc --init # 生成 tsconfig.json
跑 TS 的几种方式
| 工具 | 特点 |
|---|---|
tsx(推荐) |
直接 tsx app.ts,无配置 |
ts-node |
老牌,配置略多 |
tsc + node |
编译后跑(生产部署用) |
| Node 22+ 原生 | --experimental-strip-types(实验性) |
| bun / deno | 原生支持 TS |
tsx(最方便)
npm install -D tsx
npx tsx app.ts # 直接跑
npx tsx watch app.ts # 文件变化自动重启
package.json:
{
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
}
}
推荐 tsconfig
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
关键字段:
| 字段 | 含义 |
|---|---|
target |
编译到哪个 JS 版本 |
module |
模块系统(NodeNext 自动跟随 package.json type) |
strict |
总是开 —— 一组严格检查 |
skipLibCheck |
跳过 node_modules 类型检查(提速) |
outDir |
编译输出目录 |
基础语法
// 变量
const name: string = 'Alice';
const age: number = 30;
const active: boolean = true;
const tags: string[] = ['a', 'b'];
// 对象
interface User {
id: number;
name: string;
email?: string; // 可选
}
const u: User = { id: 1, name: 'Alice' };
// 函数
function add(a: number, b: number): number {
return a + b;
}
// 箭头
const greet = (name: string): string => `Hi, ${name}`;
// Union 类型
type Status = 'pending' | 'done' | 'failed';
let s: Status = 'done';
// 泛型
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
first([1, 2, 3]); // 类型 number
first(['a', 'b']); // 类型 string
// Promise
async function getUser(id: number): Promise<User> {
const r = await fetch(`/users/${id}`);
return r.json();
}
interface vs type
interface User {
name: string;
}
type User = {
name: string;
};
二者大部分时候等价。微小区别:
interface可重复声明合并;type不行type能定义 union / primitive 别名;interface 不行
常用规则:对象 / 类用 interface;其他用 type。
实战:Express + TS
import express, { Request, Response } from 'express';
const app = express();
app.use(express.json());
interface CreateUserBody {
name: string;
email: string;
}
app.post('/users', (req: Request<{}, {}, CreateUserBody>, res: Response) => {
const { name, email } = req.body; // 自动类型 string
res.json({ id: 1, name, email });
});
app.listen(3000);
第三方类型
# 多数库自带 .d.ts
npm install express
# 没自带的去 DefinitelyTyped 装
npm install -D @types/lodash @types/cors
迁移 JS 项目
渐进策略:
1. 装 TS + 配 tsconfig(allowJs: true)
2. 把入口文件改成 .ts
3. 逐个文件改后缀 .js → .ts,修类型错
4. 全部改完后关 allowJs
5. 开 strict 模式逐步增强
不用一夜全改——TS 允许 .js 和 .ts 共存。
实战提示
any 是逃生口(少用)
const data: any = JSON.parse(text); // 失去类型检查
// 更好:
interface Data { ... }
const data = JSON.parse(text) as Data;
unknown 比 any 安全
function parse(s: string): unknown {
return JSON.parse(s);
}
const data = parse(text);
// data 是 unknown,必须 narrow 才能用
if (typeof data === 'object' && data && 'name' in data) {
console.log(data.name);
}
satisfies(精准类型)
type Config = { host: string; port: number };
const config = {
host: 'localhost',
port: 8080,
} satisfies Config;
// config 类型精确(不被 widening),又被检查符合 Config
部署
开发用 tsx watch;生产先编译再跑:
# 构建阶段
npm run build # = tsc
# 部署阶段
node dist/index.js
或在 Docker 里多阶段构建:
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json tsconfig.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:22-alpine
WORKDIR /app
COPY --from=build /app/package*.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
CMD ["node", "dist/index.js"]
坑
- TS 不在运行时检查——
as Type是程序员的承诺,错了运行时仍崩 - 第三方库类型可能滞后或错——
@ts-ignore临时跳过,但永远不该是 PR 心态 tsc慢 → 用tsx/swc/esbuild提速- 生产不要直接跑
.ts——用 tsc 编译或 esbuild bundle
下一篇:工具链。