从零开始搭建TypeScript项目全流程详解包括环境配置文件规范常见坑点及最佳实践指南
TypeScript项目搭建完全指南
嘿,朋友!今天咱们来聊聊怎么从零开始搭一个TypeScript项目。我知道,听起来好像挺复杂的,但其实只要你跟着我的步骤走,绝对能搞定。我当年踩过的坑,现在都变成经验了,希望你别再踩了。
一、先说说为啥要用TypeScript
在你开始搭项目之前,我得先让你明白,TypeScript到底能给你带来啥好处。
说实话,刚开始我写JavaScript写得很开心,感觉啥都能干。但是随着项目越来越大,代码量越来越多,问题就开始出现了。
最大的痛点是什么? 你函数调用传错参数了,运行时才报错,你都不知道错误在哪。TypeScript的静态类型检查,就是在编译阶段帮你发现问题,而不是等到跑起来的时候才出bug。
举个例子,假设你在写一个用户登录功能:
// JavaScript时代,你可能会这么写
function login(username, password) {
return fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ username, password })
});
}
// 某天你忘记传password了
login('zhangsan'); // 运行时才会报错
改成TypeScript后:
function login(username: string, password: string): Promise<Response> {
return fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ username, password })
});
}
// TypeScript会直接报错,提示你缺少参数
login('zhangsan'); // Error: Expected 2 arguments, but got 1
看到没?TypeScript在你写代码的时候就帮你发现问题了,省得你调试半天。
二、搭建前的环境准备
在动手之前,先确认你的电脑上装好了这些东西。
2.1 安装Node.js
TypeScript需要Node.js来运行。建议去 Node.js官网 下载Long Term Support(LTS)版本。
安装完成后,打开终端(Mac/Linux用Terminal,Windows用PowerShell或CMD),输入以下命令检查:
node -v
npm -v
你应该能看到类似这样的输出:
v18.17.0
9.6.7
版本号比我写的新的话就更好了,说明你用的是更新版本。
2.2 选择包管理器
现在主流的包管理器有三个:
| 包管理器 | 特点 | 推荐度 |
|---|---|---|
| npm | Node.js自带,最通用 | ⭐⭐⭐ |
| yarn | 速度快,稳定性好 | ⭐⭐⭐⭐ |
| pnpm | 最省空间,性能优秀 | ⭐⭐⭐⭐⭐ |
我强烈推荐你用 pnpm,因为它能节省大量磁盘空间,同时构建速度也很快。安装命令:
npm install -g pnpm
安装好后验证一下:
pnpm -v
2.3 安装TypeScript
有两种方式安装TypeScript:
全局安装(适合开发环境):
pnpm add -g typescript
本地安装(推荐,更适合项目):
在项目目录里安装:
pnpm add -D typescript
这样TypeScript就作为项目的开发依赖了,每个项目都有自己的TypeScript版本,不会互相干扰。
三、初始化TypeScript项目
好了,环境都准备好了,咱们开始真正搭项目。
3.1 创建项目目录
mkdir my-typescript-project
cd my-typescript-project
3.2 初始化package.json
package.json是Node.js项目的配置文件,它记录了项目的依赖、脚本等信息。
pnpm init
运行后会生成一个简单的package.json:
{
"name": "my-typescript-project",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
3.3 初始化TypeScript配置
TypeScript需要一个配置文件来告诉编译器如何工作。最简单的初始化命令:
tsc --init
这会在项目根目录生成一个tsconfig.json文件。不过这个默认配置比较保守,咱们稍后会详细讲解怎么配置它。
四、项目目录结构设计
一个清晰的项目结构对团队协作非常重要。下面是一个推荐的目录结构:
my-typescript-project/
├── src/ # 源代码目录
│ ├── index.ts # 入口文件
│ ├── types/ # 类型定义文件
│ │ ├── user.ts
│ │ └── api.ts
│ ├── utils/ # 工具函数
│ │ ├── format.ts
│ │ └── validate.ts
│ ├── services/ # 服务层
│ │ └── userService.ts
│ └── controllers/ # 控制器层
│ └── userController.ts
├── dist/ # 编译输出目录
├── tests/ # 测试文件
│ ├── user.test.ts
│ └── validate.test.ts
├── .eslintrc.json # ESLint配置
├── .prettierrc # Prettier配置
├── tsconfig.json # TypeScript配置
├── tsconfig.build.json # 构建用配置(可选)
├── package.json
└── README.md
为什么这么设计?
src/ 放源代码:所有写的TypeScript代码都放在这里,不污染项目根目录。
dist/ 放编译产物:TypeScript编译后的JavaScript代码放在这里,这个目录应该加入.gitignore,不要提交到版本控制。
types/ 放类型定义:把公共的类型定义单独放在一个目录里,方便复用。
utils/ 放工具函数:通用的、没有业务逻辑的工具函数。
services/ 放服务层:处理业务逻辑的地方。
controllers/ 放控制器层:处理请求和响应。
tests/ 放测试文件:和源码目录分开,方便管理。
五、详解tsconfig.json配置
tsconfig.json是TypeScript项目最核心的配置文件,它告诉TypeScript编译器如何处理你的代码。咱们一行行来看:
{
// 编译选项
"compilerOptions": {
// 输出目录
"outDir": "./dist",
// 源文件目录
"rootDir": "./src",
// 目标JavaScript版本
"target": "ES2020",
// 模块系统
"module": "CommonJS",
// 模块解析策略
"moduleResolution": "node",
// 是否生成声明文件
"declaration": true,
// 声明文件输出目录
"declarationDir": "./dist/types",
// 是否生成map文件
"sourceMap": true,
// 严格模式
"strict": true,
// 是否跳过类型检查的库文件
"skipLibCheck": true,
// 允许从没有声明文件的模块导入
"allowSyntheticDefaultImports": true,
// 允许import json模块
"esModuleInterop": true,
// 类型根目录
"typeRoots": ["./node_modules/@types", "./src/types"],
// 包含哪些文件
"include": ["src/**/*"],
// 排除哪些文件
"exclude": ["node_modules", "dist", "tests"]
},
// 其他配置
"ts-node": {
"transpileOnly": true
}
}
5.1 关键配置项详解
outDir:编译后的JavaScript文件输出到哪个目录。
rootDir:TypeScript源文件的根目录。
target:编译后的JavaScript版本。常用的有:
ES3:最老的,兼容性最好ES5:现在大多数项目都用这个ES2015/ES6:支持箭头函数、类等现代语法ES2020:支持可选链、空值合并等操作符
module:模块系统。CommonJS是Node.js的默认模块系统,ESModule是现代JavaScript的标准模块系统。根据你用的运行时选择。
strict:开启严格模式。这是最重要的配置之一,它会开启一系列严格检查,帮助你在写代码时发现更多问题。
skipLibCheck:跳过对node_modules中类型文件的检查。能加快编译速度,推荐开启。
esModuleInterop:允许使用ESModule的import语法导入CommonJS模块。这个也强烈推荐开启。
六、添加Lint和代码格式化
代码风格统一对团队协作很重要。我们推荐用ESLint做代码检查,用Prettier做代码格式化。
6.1 安装依赖
pnpm add -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin prettier eslint-config-prettier
6.2 创建ESLint配置文件
在项目根目录创建.eslintrc.json:
{
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module"
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"plugins": ["@typescript-eslint"],
"rules": {
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-unused-vars": "error",
"no-console": "warn"
},
"env": {
"node": true,
"es2020": true
}
}
6.3 创建Prettier配置文件
创建.prettierrc文件:
{
"semi": false,
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 80,
"tabWidth": 2
}
6.4 添加Git忽略文件
创建.gitignore文件:
# 依赖
node_modules/
# 编译输出
dist/
build/
# 环境变量
.env
.env.local
# IDE
.idea/
.vscode/
*.swp
*.swo
# 测试覆盖率
coverage/
# 日志
*.log
# OS
.DS_Store
Thumbs.db
七、编写第一个TypeScript文件
现在咱们来写代码了!
7.1 创建入口文件
在src/目录下创建index.ts:
// src/index.ts
import { UserService } from './services/userService';
import { formatUserName } from './utils/format';
const userService = new UserService();
async function main(): Promise<void> {
const users = await userService.getAllUsers();
for (const user of users) {
const displayName = formatUserName(user);
console.log(`用户: ${displayName}`);
}
}
main().catch(console.error);
7.2 创建工具函数
// src/utils/format.ts
import { User } from '../types/user';
/**
* 格式化用户显示名称
* @param user 用户对象
* @returns 格式化后的名称
*/
export function formatUserName(user: User): string {
if (user.nickname) {
return `${user.nickname}(${user.username})`;
}
return user.username;
}
/**
* 格式化日期
* @param date 日期对象或时间戳
* @returns 格式化后的日期字符串
*/
export function formatDate(date: Date | number): string {
const d = typeof date === 'number' ? new Date(date) : date;
return d.toISOString().split('T')[0];
}
7.3 创建类型定义
// src/types/user.ts
export interface User {
id: number;
username: string;
nickname?: string;
email: string;
createdAt: Date;
updatedAt: Date;
}
export type UserCreateInput = Omit<User, 'id' | 'createdAt' | 'updatedAt'>;
export type UserUpdateInput = Partial<UserCreateInput>;
export type UserId = number;
export interface UserListResponse {
users: User[];
total: number;
page: number;
pageSize: number;
}
7.4 创建服务层
// src/services/userService.ts
import { User, UserCreateInput, UserUpdateInput, UserListResponse } from '../types/user';
export class UserService {
// 模拟数据库
private users: User[] = [];
private nextId = 1;
/**
* 获取所有用户
*/
async getAllUsers(): Promise<User[]> {
// 模拟异步操作
return new Promise((resolve) => {
setTimeout(() => resolve([...this.users]), 100);
});
}
/**
* 根据ID获取用户
*/
async getUserById(id: number): Promise<User | undefined> {
return new Promise((resolve) => {
setTimeout(() => {
const user = this.users.find((u) => u.id === id);
resolve(user);
}, 50);
});
}
/**
* 创建用户
*/
async createUser(input: UserCreateInput): Promise<User> {
const user: User = {
...input,
id: this.nextId++,
createdAt: new Date(),
updatedAt: new Date(),
};
this.users.push(user);
return Promise.resolve(user);
}
/**
* 更新用户
*/
async updateUser(id: number, input: UserUpdateInput): Promise<User | undefined> {
const index = this.users.findIndex((u) => u.id === id);
if (index === -1) {
return undefined;
}
this.users[index] = {
...this.users[index],
...input,
id,
createdAt: this.users[index].createdAt,
updatedAt: new Date(),
};
return Promise.resolve(this.users[index]);
}
/**
* 删除用户
*/
async deleteUser(id: number): Promise<boolean> {
const index = this.users.findIndex((u) => u.id === id);
if (index === -1) {
return false;
}
this.users.splice(index, 1);
return Promise.resolve(true);
}
}
八、配置构建脚本
现在咱们来配置package.json中的脚本,让项目能够正常编译和运行。
{
"name": "my-typescript-project",
"version": "1.0.0",
"description": "从零开始搭建TypeScript项目",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"build:watch": "tsc --watch",
"dev": "ts-node src/index.ts",
"start": "node dist/index.js",
"lint": "eslint src --ext .ts",
"lint:fix": "eslint src --ext .ts --fix",
"format": "prettier --write \"src/**/*.ts\"",
"test": "jest",
"prepare": "pnpm build"
},
"keywords": ["typescript", "project"],
"author": "Your Name",
"license": "MIT"
}
现在你可以运行以下命令来编译项目:
pnpm build
编译成功后,你会看到dist/目录下生成了编译后的JavaScript文件。
运行项目:
pnpm start
开发模式下运行(不需要编译):
pnpm dev
九、配置测试环境
测试是项目质量保证的重要环节。咱们用Jest来做单元测试。
9.1 安装测试依赖
pnpm add -D jest ts-jest @types/jest
9.2 初始化Jest配置
npx jest --init
会生成一个jest.config.js文件,咱们修改一下:
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
moduleFileExtensions: ['ts', 'js', 'json'],
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts',
],
coverageDirectory: 'coverage',
};
9.3 编写测试用例
// tests/userService.test.ts
import { UserService } from '../src/services/userService';
import { UserCreateInput } from '../src/types/user';
describe('UserService', () => {
let userService: UserService;
beforeEach(() => {
userService = new UserService();
});
describe('createUser', () => {
it('应该能创建新用户', async () => {
const input: UserCreateInput = {
username: 'testuser',
email: 'test@example.com',
nickname: '测试用户',
};
const user = await userService.createUser(input);
expect(user.id).toBe(1);
expect(user.username).toBe('testuser');
expect(user.email).toBe('test@example.com');
expect(user.nickname).toBe('测试用户');
expect(user.createdAt).toBeInstanceOf(Date);
expect(user.updatedAt).toBeInstanceOf(Date);
});
it('创建用户后应该能通过ID找到', async () => {
const input: UserCreateInput = {
username: 'testuser',
email: 'test@example.com',
};
const user = await userService.createUser(input);
const foundUser = await userService.getUserById(user.id);
expect(foundUser).toBeDefined();
expect(foundUser?.id).toBe(user.id);
});
});
describe('getAllUsers', () => {
it('初始状态下应该返回空数组', async () => {
const users = await userService.getAllUsers();
expect(users).toEqual([]);
});
it('创建用户后应该能获取到所有用户', async () => {
await userService.createUser({
username: 'user1',
email: 'user1@example.com',
});
await userService.createUser({
username: 'user2',
email: 'user2@example.com',
});
const users = await userService.getAllUsers();
expect(users).toHaveLength(2);
});
});
});
运行测试:
pnpm test
十、常见坑点及解决方案
在搭建TypeScript项目的过程中,你会遇到各种各样的问题。下面我整理了最常见的坑点,以及解决方案。
10.1 找不到模块
问题描述:
import { foo } from './utils'; // Error: Cannot find module './utils'
解决方案:
- 检查文件名是否正确,TypeScript对文件名大小写敏感
- 确保文件路径正确
- 如果导入的是
.js文件,确保allowJs配置开启 - 检查
tsconfig.json中的paths配置
10.2 类型推导失败
问题描述:
const data = [{ id: 1, name: 'test' }];
// TypeScript不知道data的类型
解决方案:
interface Item {
id: number;
name: string;
}
const data: Item[] = [{ id: 1, name: 'test' }];
// 或者使用类型推导
const data: Item[] = [{ id: 1, name: 'test' }] as const;
10.3 any类型的滥用
问题描述:
function processData(data: any) {
// 完全放弃了类型检查
return data;
}
解决方案:
// 使用具体的类型
function processData(data: string | number | object) {
if (typeof data === 'string') {
return data.toUpperCase();
}
return data;
}
// 或者使用泛型
function processData<T>(data: T): T {
return data;
}
10.4 循环依赖
问题描述:
// a.ts
import { b } from './b';
export const a = b;
// b.ts
import { a } from './a';
export const b = a;
解决方案:
- 重构代码,打破循环依赖
- 使用
import type代替import:
// a.ts
import type { BType } from './b';
export const a: BType = { ... };
// b.ts
import type { AType } from './a';
export const b: AType = { ... };
10.5 TypeScript版本不一致
问题描述:
全局安装的TypeScript版本和项目本地安装的不一致。
解决方案:
- 在项目中使用本地安装的TypeScript:
./node_modules/.bin/tsc --version
- 在package.json中指定脚本:
{
"scripts": {
"build": "tsc"
}
}
pnpm会自动使用项目本地安装的TypeScript。
10.6 第三方库没有类型定义
问题描述:
import someLib from 'some-library'; // Error: Cannot find module 'some-library'
解决方案:
- 查找官方类型定义:
pnpm add -D @types/some-library
- 如果没有官方类型定义,可以创建一个
:any的类型声明:
// types/declarations.d.ts
declare module 'some-library' {
const someLib: any;
export default someLib;
}
10.7 编译速度慢
问题描述:
大型项目编译时间很长,影响开发效率。
解决方案:
- 使用增量编译:
{
"compilerOptions": {
"incremental": true
}
}
- 使用
skipLibCheck跳过类型检查:
{
"compilerOptions": {
"skipLibCheck": true
}
}
- 使用
tsconfig的exclude排除不必要的文件:
{
"compilerOptions": {
"exclude": ["node_modules", "dist", "tests"]
}
}
十一、最佳实践总结
11.1 严格模式必开
{
"compilerOptions": {
"strict": true
}
}
严格模式能帮你发现很多潜在问题,不要为了省事关掉它。
11.2 尽量避免使用any
// 不推荐
const value: any = getData();
// 推荐
const value: unknown = getData();
if (typeof value === 'string') {
console.log(value.toUpperCase());
}
11.3 使用类型别名和接口
// 类型别名,适合基本类型和联合类型
type UserId = number;
type Status = 'pending' | 'active' | 'inactive';
// 接口,适合对象类型
interface User {
id: UserId;
name: string;
status: Status;
}
11.4 函数参数和返回值都要有类型
// 不推荐
function add(a, b) {
return a + b;
}
// 推荐
function add(a: number, b: number): number {
return a + b;
}
11.5 合理使用泛型
// 泛型函数
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
// 泛型类
class ApiResponse<T> {
constructor(
public data: T,
public success: boolean,
public message: string
) {}
}
11.6 错误处理要规范
// 定义统一的错误类型
class AppError extends Error {
constructor(
public statusCode: number,
message: string
) {
super(message);
this.name = 'AppError';
}
}
// 使用
try {
const user = await userService.getUserById(id);
if (!user) {
throw new AppError(404, '用户不存在');
}
} catch (error) {
if (error instanceof AppError) {
console.error(`错误 ${error.statusCode}: ${error.message}`);
}
}
11.7 使用环境变量管理配置
// src/config/env.ts
interface Config {
port: number;
databaseUrl: string;
jwtSecret: string;
}
const config: Config = {
port: Number(process.env.PORT) || 3000,
databaseUrl: process.env.DATABASE_URL || 'mongodb://localhost:27017/mydb',
jwtSecret: process.env.JWT_SECRET || 'your-secret-key',
};
export default config;
创建.env文件:
PORT=3000
DATABASE_URL=mongodb://localhost:27017/mydb
JWT_SECRET=your-secret-key
十二、进阶配置
12.1 多配置tsconfig
如果你的项目有复杂的结构,可以创建多个tsconfig:
// tsconfig.json - 基础配置
{
"compilerOptions": {
"strict": true,
"target": "ES2020",
"module": "CommonJS",
"esModuleInterop": true
}
}
// tsconfig.build.json - 构建配置
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"sourceMap": true
},
"include": ["src"],
"exclude": ["node_modules", "dist", "tests"]
}
// tsconfig.test.json - 测试配置
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true,
"strict": false
},
"include": ["src", "tests"]
}
12.2 使用ESModule
如果你希望使用ESModule而不是CommonJS:
{
"compilerOptions": {
"module": "ES2020",
"target": "ES2020"
}
}
然后package.json中也要加上:
{
"type": "module"
}
十三、总结
好了,到这里咱们已经把TypeScript项目从0到1全部搭完了。总结一下关键步骤:
- 安装环境:Node.js、pnpm、TypeScript
- 初始化项目:
pnpm init、tsc --init - 配置tsconfig.json:根据你的需求调整
- 安装Lint工具:ESLint + Prettier
- 搭建项目结构:src/、dist/、tests/
- 编写代码:遵循最佳实践
- 配置构建脚本:build、dev、test
- 添加测试:Jest
记住,TypeScript的核心价值在于类型安全。当你开始写代码的时候,尽量给所有变量、函数参数、返回值都加上类型,这样能帮你避免很多bug。
如果你在搭建过程中遇到任何问题,随时可以来问我。编程这条路,踩坑是常态,关键是从坑里学到东西。
祝你项目搭建顺利!🎉
