TypeScript 与 Node.js

TypeScript 是 JavaScript 的超集,它在 JavaScript 的基础上添加了静态类型系统和其他现代语言特性。将 TypeScript 与 Node.js 结合使用,可以显著提升代码的可维护性和开发体验,尤其是在大型项目中。本章将带你从零开始搭建 TypeScript + Node.js 环境,并探索其最佳实践。

1. 为什么要在 Node.js 中使用 TypeScript?

TypeScript 为 Node.js 开发带来了许多好处:

  • 类型安全:在编译时捕获类型错误,减少运行时 bug。
  • 更好的 IDE 支持:自动补全、接口提示、重构更加智能。
  • 可维护性:明确的类型定义使代码更易读和理解。
  • 现代 JavaScript 特性:TypeScript 支持最新的 ECMAScript 语法,并可编译到目标 Node.js 版本。
  • 声明文件:可以轻松使用现有的 JavaScript 库(通过 DefinitelyTyped)。

2. 环境搭建

2.1 初始化项目

mkdir my-ts-node-app
cd my-ts-node-app
npm init -y

2.2 安装 TypeScript 及相关依赖

npm install --save-dev typescript @types/node ts-node nodemon
  • typescript:TypeScript 编译器。
  • @types/node:Node.js 核心模块的类型定义。
  • ts-node:用于直接运行 TypeScript 文件(开发时方便)。
  • nodemon:监听文件变化自动重启。

2.3 创建 tsconfig.json

使用以下命令生成默认配置:

npx tsc --init

然后根据需要修改,一个常见的 Node.js 配置如下:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "commonjs",
    "lib": ["ES2022"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

2.4 配置 package.json 脚本

"scripts": {
  "start": "node dist/index.js",
  "dev": "nodemon --exec ts-node src/index.ts",
  "build": "tsc",
  "clean": "rm -rf dist"
}

dev 脚本使用 ts-node 直接运行 TypeScript 源文件,适合开发;build 编译为 JavaScript;start 运行编译后的代码,适合生产。

3. 编写第一个 TypeScript 文件

创建 src/index.ts

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

const user: string = 'TypeScript';
console.log(greet(user));

运行 npm run dev 即可看到输出。

4. 使用 Node.js 核心模块

TypeScript 能够识别 @types/node 提供的类型,例如使用 fs 模块:

import fs from 'fs/promises';

async function readFile(path: string): Promise<string> {
  try {
    const data = await fs.readFile(path, 'utf8');
    return data;
  } catch (err) {
    console.error(err);
    throw err;
  }
}

5. 与 Express 集成

安装 Express 及其类型定义:

npm install express
npm install --save-dev @types/express

创建一个简单的 Express 应用 src/app.ts

import express, { Request, Response } from 'express';

const app = express();
const port = process.env.PORT || 3000;

app.use(express.json());

interface User {
  id: number;
  name: string;
}

let users: User[] = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' }
];

app.get('/users', (req: Request, res: Response) => {
  res.json(users);
});

app.get('/users/:id', (req: Request, res: Response) => {
  const id = parseInt(req.params.id);
  const user = users.find(u => u.id === id);
  if (!user) {
    return res.status(404).json({ error: 'User not found' });
  }
  res.json(user);
});

app.post('/users', (req: Request, res: Response) => {
  const newUser: User = {
    id: users.length + 1,
    name: req.body.name
  };
  users.push(newUser);
  res.status(201).json(newUser);
});

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
});

6. 类型定义文件(.d.ts)

当使用没有类型定义的 JavaScript 库时,你可以自己编写声明文件,或者从 DefinitelyTyped 安装(@types/package-name)。如果需要自定义类型,可以创建 src/types 目录存放 .d.ts 文件。例如 src/types/custom.d.ts

declare module 'some-library' {
  export function doSomething(param: string): void;
}

7. 调试 TypeScript

在 VSCode 中调试 TypeScript 非常方便。创建 .vscode/launch.json

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Launch Program",
      "runtimeArgs": ["-r", "ts-node/register"],
      "args": ["${workspaceFolder}/src/index.ts"]
    }
  ]
}

这样可以直接调试 TypeScript 源文件。或者使用 nodemon 配合 --inspect 也可以。

8. 使用路径别名

为了让模块导入更清晰,可以在 tsconfig.json 中配置路径别名:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@models/*": ["src/models/*"]
    }
  }
}

然后在代码中使用:

import User from '@/models/User';

注意:运行时需要额外工具(如 tsconfig-paths)来解析别名,安装 tsconfig-paths 并在启动时注册:

npm install tsconfig-paths

使用 node -r tsconfig-paths/register -r ts-node/register src/index.ts

9. 错误处理

TypeScript 可以定义自定义错误类:

export class ApiError extends Error {
  public statusCode: number;
  constructor(message: string, statusCode: number) {
    super(message);
    this.statusCode = statusCode;
    this.name = 'ApiError';
  }
}

然后在中间件中处理:

app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
  if (err instanceof ApiError) {
    res.status(err.statusCode).json({ error: err.message });
  } else {
    res.status(500).json({ error: 'Internal server error' });
  }
});

10. 构建与部署

运行 npm run build 生成 JavaScript 文件到 dist 目录。部署时只需上传 distpackage.jsonpackage-lock.jsonnode_modules(或者使用 npm ci --production 安装生产依赖)。然后在生产环境运行 npm start(指向编译后的入口)。

11. 最佳实践

  • 始终开启 strict 模式,获得最强的类型检查。
  • 为所有公共 API 编写接口或类型。
  • 使用 import type 导入仅类型信息,避免运行时依赖。
  • 将类型定义放在靠近实现的位置,而不是全局声明。
  • 利用 ESLint + @typescript-eslint 进行代码风格和质量检查。
  • 使用 prettier 统一代码格式。
总结: TypeScript 与 Node.js 的结合为服务器端开发带来了类型安全和现代语言特性。通过合理的项目配置、类型定义和工具链,你可以构建出健壮、可维护的应用程序。本章涵盖了从零搭建到高级用法的全过程,希望能帮助你在实际项目中顺利使用 TypeScript。