01. 从零搭建 TypeScript 项目
搭建一个 TypeScript 项目不光是装个包那么简单。现代 TS 项目推荐配套:
1. Node.js 项目——ts-node 或 tsx 直接运行 TS 文件,省去先编译再运行的步骤。
2. 前端项目——Vite 或 Next.js 自带 TS 支持,创建项目时就选 TS 模板。
3. 构建输出——tsc 编译成 JS 输出到 dist/ 目录。
4. 类型检查——tsc --noEmit 只检查不输出,放到 CI 和 pre-commit hook 里。
5. 格式化和 lint——Prettier + ESLint(typescript-eslint)保持代码风格一致。
bash
# Node.js 项目
mkdir my-project && cd my-project
npm init -y
npm install -D typescript @types/node tsx
tsc --init
# 运行 TS 文件
npx tsx src/index.ts
# 前端项目
npm create vite@latest my-app -- --template react-ts
# 只检查类型
npx tsc --noEmit02. tsconfig.json 详细配置指南
一个生产级别的 tsconfig.json:
target——编译输出到哪个 JS 版本。Node.js 18 推荐 ES2022。
module——模块系统。Node.js 用 commonjs 或 nodenext,前端打包项目用 ESNext。
moduleResolution——模块解析策略。node 或 bundler。
rootDir / outDir——源码和输出分开。
esModuleInterop——让默认导出和命名导出互操作更顺畅。
skipLibCheck——跳过 .d.ts 文件的类型检查(快很多)。
forceConsistentCasingInFileNames——文件名大小写一致(Windows 上区分,Mac 上不区分,不开容易出问题)。
declaration: true——生成 .d.ts 类型声明文件(如果你要发布为 npm 包)。
sourceMap: true——生成 source map,调试时能看到原始 TS 代码。
json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"rootDir": "./src",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}03. 声明文件 .d.ts——给 JS 加类型
.d.ts 文件是纯类型声明文件——不包含运行时代码,只告诉 TypeScript「这个模块/对象的类型长这样」。
什么时候需要写 .d.ts:
1. 你用了一个没有官方类型的 JS 库——写 .d.ts 文件声明模块类型。
2. 你在项目中定义了全局类型或全局变量。
3. 你发布 npm 包——写 .d.ts 让用户获得类型提示。
declare module 'module-name'——给一个 JS 模块声明类型。如果只用到模块的少数几个方法,只声明你需要的就行。
declare global——声明全局变量。例如 declare var __VERSION__: string。
全局类型文件 types/*.d.ts——把常用的全局类型(如 API 响应的通用包装)放这里,不用到处 import。
typescript
// types/global.d.ts
// 声明一个没有类型的 JS 模块
declare module 'legacy-lib' {
export function doSomething(input: string): number;
}
// 声明全局变量
declare var __VERSION__: string;
declare var __BUILD_TIME__: number;
// 声明静态资源导入
// declare module '*.svg' {
// const content: React.FC<React.SVGProps<SVGSVGElement>>;
// export default content;
// }
declare module '*.css' {
const content: Record<string, string>;
export default content;
}04. ESLint + Prettier 的 TS 配置
TypeScript 的 lint 以前用 TSLint,现在已经废弃。现在都用 ESLint + @typescript-eslint。
核心包:eslint、@typescript-eslint/parser、@typescript-eslint/eslint-plugin。
Prettier 负责格式化(缩进、引号、分号),ESLint 负责代码质量(未使用的变量、错误的类型断言、detect async 里的 await 缺失)。两者分工明确。
eslint-config-prettier——关闭 ESLint 里跟 Prettier 冲突的规则。确保顺序:ESLint 先跑,Prettier 后格式化。
CI 里加上 npx eslint 和 npx prettier --check,以及 npx tsc --noEmit。三道关过不了不能合并。
bash
# 安装
npm install -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
npm install -D prettier eslint-config-prettier
# .eslintrc.json
{
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
]
}
# package.json 的 lint 脚本
{
"scripts": {
"lint": "eslint 'src/**/*.{ts,tsx}'",
"format": "prettier --write 'src/**/*.{ts,tsx}'",
"typecheck": "tsc --noEmit"
}
}CI 管线里 typecheck、lint、format check 三个都跑一遍,确保代码质量。pre-commit hook 只跑 lint-staged(快),完整检查交给 CI。
05. 生产环境的 TS 最佳实践
1. strict: true 从项目第一天就开启。后面开会有几百个报错很难改。
2. 避免 any——用 unknown + 类型守卫替代。as any 越多 TS 的价值越低。
3. 类型收窄——联合类型变量用 if/switch 缩窄类型范围,不要到处 as 断言。
4. 泛型约束——extends 约束泛型参数,不要写成裸 T。
5. 给异步函数声明返回类型——避免意外返回 Promise<void> 而是 Promise<SpecificType>。
6. import type——只用到的类型用 import type 导入,编译后不留下 import 语句。
7. 善用 enum 替代(union type / as const object)——不用生成额外运行时代码的方案。
typescript
// 好的 TS 实践
// 1. 避免 any
function safe(data: unknown) {
if (typeof data === 'string') {
return data.toUpperCase();
}
return null;
}
// 2. import type——只导类型不导值
import type { User } from './types';
import { getUser } from './api'; // 这个是值,正常 import
// 3. as const 替代 enum
// const STATUS = {
// ACTIVE: 'active',
// INACTIVE: 'inactive'
// } as const;
// type Status = typeof STATUS[keyof typeof STATUS];import type 在编译后会被完全删除——不会出现在输出的 JS 里。用这能避免循环依赖和减少打包体积。
知识测验
第 1/5 题正确 0
esModuleInterop 解决什么问题?