第一次接触TypeScript是在2016年,当时团队正在重构一个大型电商后台系统。JavaScript的弱类型特性让我们在维护过程中吃尽了苦头——一个拼写错误可能要运行到特定分支才会暴露,接口数据结构变更时要在十几个文件中手动检查。自从切换到TypeScript后,这些痛点都得到了显著改善。
TypeScript作为JavaScript的超集,最核心的价值在于类型安全。想象你正在搭建乐高积木,TypeScript就像提供了形状匹配的凹槽,确保每个零件都能严丝合缝地拼接。这种特性在全栈开发中尤为重要,因为你需要同时处理:
我常用的技术栈组合是React+Express+TypeORM,这个组合的亮点在于端到端类型共享。比如定义一个用户类型后,可以在前端表单校验、API路由参数、数据库实体中复用,修改时只需调整一处。
新手常犯的错误是全局安装TypeScript,这可能导致不同项目版本冲突。推荐使用以下方式:
bash复制# 在项目目录中
npm init -y
npm install typescript --save-dev
npx tsc --init
生成的tsconfig.json需要重点关注这些配置项:
json复制{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}
这是我经过多个项目验证的目录结构:
code复制project/
├── client/ # 前端代码
│ ├── src/
│ └── tsconfig.json
├── server/ # 后端代码
│ ├── src/
│ └── tsconfig.json
├── shared/ # 共享类型定义
│ └── types.ts
└── package.json
关键技巧是在根目录的package.json中添加workspaces配置:
json复制{
"workspaces": [
"client",
"server",
"shared"
]
}
这样可以通过import { User } from '@shared/types'实现跨项目类型共享。
接口(interface)和类型别名(type)看似相似,但在全栈开发中有明确分工:
typescript复制// 适合定义DTO和数据模型
interface User {
id: number;
name: string;
email: string;
}
// 适合组合类型
type UserResponse = {
data: User;
meta: {
page: number;
total: number;
};
};
实际项目中我会用接口定义数据库实体,用类型别名处理API响应格式。
这是我在实际项目中封装的API响应工具:
typescript复制// shared/api-types.ts
export interface ApiResponse<T = any> {
code: number;
data: T;
message?: string;
}
// 使用示例
export function getUser(): Promise<ApiResponse<User>> {
return axios.get('/api/user');
}
这种模式让前端能准确知道返回的数据结构,配合axios拦截器还能实现统一的错误处理。
在shared/types.ts中定义核心类型:
typescript复制export interface Product {
id: string;
name: string;
price: number;
inventory: number;
}
export type CartItem = {
product: Product;
quantity: number;
};
使用Express+TypeScript的典型路由:
typescript复制import { Request, Response } from 'express';
import { Product } from '@shared/types';
const products: Product[] = [...];
app.get('/api/products', (req: Request, res: Response<Product[]>) => {
res.json(products);
});
app.post('/api/checkout',
(req: Request<{}, {}, CartItem[]>, res: Response<{ orderId: string }>) => {
// 实现下单逻辑
}
);
React组件中的类型安全请求:
typescript复制function ProductList() {
const [products, setProducts] = useState<Product[]>([]);
useEffect(() => {
axios.get<Product[]>('/api/products')
.then(res => setProducts(res.data));
}, []);
return (
<ul>
{products.map(p => (
<li key={p.id}>{p.name} - ${p.price}</li>
))}
</ul>
);
}
typescript复制// server/src/entities/User.ts
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column({ unique: true })
email: string;
}
处理数据库实体与DTO的转换:
typescript复制// server/src/services/userService.ts
import { User } from '../entities/User';
import { CreateUserDto } from '../dtos';
export class UserService {
async createUser(dto: CreateUserDto): Promise<User> {
const user = new User();
user.name = dto.name;
user.email = dto.email;
return await user.save();
}
}
处理联合类型时的类型收窄:
typescript复制function isAdmin(user: User | Admin): user is Admin {
return 'permissions' in user;
}
function showDashboard(user: User | Admin) {
if (isAdmin(user)) {
// 这里user会被推断为Admin类型
console.log(user.permissions);
}
}
定义路由路径类型:
typescript复制type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type ApiRoute = `/api/${string}`;
function request(method: HttpMethod, url: ApiRoute) {
// 实现请求逻辑
}
// 正确使用
request('GET', '/api/products');
// 类型错误
request('PATCH', '/products');
在tsconfig.json中添加这些生产环境配置:
json复制{
"compilerOptions": {
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"sourceMap": false
}
}
Dockerfile的TypeScript优化配置:
dockerfile复制FROM node:16
WORKDIR /app
COPY package*.json ./
COPY tsconfig.json ./
RUN npm install
COPY . .
RUN npm run build
CMD ["node", "dist/main.js"]
遇到没有类型定义的库时,可以这样处理:
bash复制npm install --save-dev @types/库名
typescript复制// src/types/module.d.ts
declare module 'untyped-module' {
export function someFunction(arg: string): void;
}
扩展第三方库的类型定义:
typescript复制// 扩展Express的Request类型
declare global {
namespace Express {
interface Request {
user?: {
id: string;
role: string;
};
}
}
}
启用tsconfig.json中的增量编译:
json复制{
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": "./.tsbuildinfo"
}
}
对于大型项目,使用项目引用拆分代码:
json复制// tsconfig.base.json
{
"compilerOptions": {
"composite": true
},
"references": [
{ "path": "./packages/core" },
{ "path": "./packages/utils" }
]
}
使用Jest时确保测试代码也通过类型检查:
typescript复制// __tests__/userService.test.ts
import { UserService } from '../services/userService';
describe('UserService', () => {
let service: UserService;
beforeEach(() => {
service = new UserService();
});
test('createUser should return user with id', async () => {
const user = await service.createUser({
name: 'Test',
email: 'test@example.com'
});
expect(user.id).toBeDefined();
});
});
使用类型安全的API测试:
typescript复制import { ApiResponse } from '@shared/types';
test('GET /api/products returns valid structure', async () => {
const res = await request(app)
.get('/api/products');
const data: ApiResponse<Product[]> = res.body;
expect(data.code).toBe(200);
expect(Array.isArray(data.data)).toBe(true);
});
将JavaScript项目逐步迁移到TypeScript:
发现重复类型定义时:
typescript复制// 重构前
function getUser(): Promise<{ id: string; name: string }> {...}
function updateUser(user: { id: string; name: string }) {...}
// 重构后
type UserBasic = {
id: string;
name: string;
};
function getUser(): Promise<UserBasic> {...}
function updateUser(user: UserBasic) {...}
确保调试时能定位到源码:
json复制{
"compilerOptions": {
"sourceMap": true,
"inlineSources": true
}
}
使用类型推导检查复杂类型:
typescript复制type ComplexType = ...;
// 鼠标悬停在DebugType上查看实际类型
type DebugType = ComplexType extends infer T ? T : never;
推荐配置:
json复制{
"compilerOptions": {
"noImplicitAny": true,
"strictNullChecks": true,
"noUnusedParameters": true
}
}
审查TypeScript代码时重点关注:
定义带类型的函数组件:
typescript复制interface Props {
title: string;
count: number;
onIncrement?: () => void;
}
const Counter: React.FC<Props> = ({ title, count, onIncrement }) => {
return (
<div>
<h2>{title}</h2>
<span>{count}</span>
{onIncrement && <button onClick={onIncrement}>+</button>}
</div>
);
};
使用TypeScript的setup语法:
typescript复制<script lang="ts">
import { defineComponent, ref } from 'vue';
export default defineComponent({
setup() {
const count = ref(0); // 自动推断为Ref<number>
function increment() {
count.value++;
}
return { count, increment };
}
});
</script>
利用装饰器实现类型安全:
typescript复制@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get(':id')
async findOne(@Param('id') id: string): Promise<User> {
return this.usersService.findOne(+id);
}
}
自定义中间件类型:
typescript复制import { RequestHandler } from 'express';
interface AuthRequest extends Request {
user: {
id: string;
role: string;
};
}
export const authMiddleware: RequestHandler<{}, any, any, {}, AuthRequest> =
(req, res, next) => {
// 实现认证逻辑
req.user = { id: '123', role: 'admin' };
next();
};
使用TypeORM的QueryBuilder保持类型安全:
typescript复制const users = await userRepository
.createQueryBuilder('user')
.leftJoinAndSelect('user.posts', 'post')
.where('user.age > :age', { age: 18 })
.orderBy('user.name', 'ASC')
.getMany(); // 返回类型为User[]
类型安全的事务封装:
typescript复制async function runInTransaction<T>(
work: (manager: EntityManager) => Promise<T>
): Promise<T> {
return getManager().transaction(async manager => {
return await work(manager);
});
}
// 使用示例
await runInTransaction(async manager => {
const user = manager.create(User, { name: 'New' });
await manager.save(user);
return user;
});
使用protobuf生成类型:
proto复制syntax = "proto3";
service UserService {
rpc GetUser (UserRequest) returns (UserResponse);
}
message UserRequest {
string id = 1;
}
message UserResponse {
string id = 1;
string name = 2;
string email = 3;
}
定义RabbitMQ消息类型:
typescript复制interface OrderMessage {
orderId: string;
userId: string;
items: Array<{
productId: string;
quantity: number;
}>;
}
channel.consume('orders', (msg) => {
if (msg) {
const content: OrderMessage = JSON.parse(msg.content.toString());
// 处理消息
}
});
创建类型安全的store:
typescript复制import { configureStore } from '@reduxjs/toolkit';
const store = configureStore({
reducer: {
users: usersReducer,
posts: postsReducer
}
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
类型安全的Context实现:
typescript复制interface AuthContextType {
user: User | null;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
}
const AuthContext = React.createContext<AuthContextType | null>(null);
function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth必须在AuthProvider内使用');
}
return context;
}
定义性能指标类型:
typescript复制interface PerformanceMetrics {
pageLoad: number;
apiResponseTimes: Record<string, number>;
memoryUsage: {
jsHeapSizeLimit: number;
totalJSHeapSize: number;
usedJSHeapSize: number;
};
}
function sendMetrics(metrics: PerformanceMetrics) {
// 发送到监控系统
}
类型安全的错误处理:
typescript复制interface TrackedError extends Error {
code?: string;
context?: Record<string, unknown>;
isOperational?: boolean;
}
function trackError(error: TrackedError) {
// 错误上报逻辑
}
在CI中添加类型检查步骤:
yaml复制steps:
- name: Install dependencies
run: npm ci
- name: Type check
run: npx tsc --noEmit
- name: Run tests
run: npm test
使用类型安全的版本检查:
typescript复制interface DependencyCheck {
name: string;
current: string;
latest: string;
isOutdated: boolean;
}
function checkDependencies(): Promise<DependencyCheck[]> {
// 实现检查逻辑
}
自动生成API文档:
bash复制npm install --save-dev typedoc
npx typedoc --out docs src/index.ts
确保文档中的代码示例通过类型检查:
typescript复制// 在测试文件中验证示例
test('文档示例1', () => {
const example = () => {
// 从文档复制的示例代码
interface Point {
x: number;
y: number;
}
const p: Point = { x: 10, y: 20 };
return p;
};
expect(example()).toEqual({ x: 10, y: 20 });
});
定义安全的输入类型:
typescript复制type SanitizedString = string & { __brand: 'Sanitized' };
function sanitize(input: string): SanitizedString {
// 实现清理逻辑
return input.replace(/<script.*?>.*?<\/script>/gi, '') as SanitizedString;
}
function saveContent(content: SanitizedString) {
// 安全地保存内容
}
类型安全的权限系统:
typescript复制type Permission = 'read' | 'write' | 'delete';
interface UserWithPermissions {
id: string;
permissions: Permission[];
}
function checkPermission(
user: UserWithPermissions,
required: Permission
): boolean {
return user.permissions.includes(required);
}
定义翻译键类型:
typescript复制type TranslationKey =
| 'home.title'
| 'home.subtitle'
| 'button.submit'
| 'button.cancel';
const translations: Record<TranslationKey, string> = {
'home.title': 'Welcome',
'home.subtitle': 'Enjoy your stay',
'button.submit': 'Submit',
'button.cancel': 'Cancel'
};
function t(key: TranslationKey): string {
return translations[key];
}
类型安全的语言包:
typescript复制interface LocaleResources {
common: {
buttons: {
ok: string;
cancel: string;
};
};
pages: {
home: {
title: string;
};
};
}
const en: LocaleResources = {
common: {
buttons: {
ok: 'OK',
cancel: 'Cancel'
}
},
pages: {
home: {
title: 'Home'
}
}
};
定义类型化的图表配置:
typescript复制interface ChartData<T = number | string> {
labels: string[];
datasets: Array<{
label: string;
data: T[];
backgroundColor?: string | string[];
}>;
}
function renderBarChart(data: ChartData<number>) {
// 渲染柱状图
}
function renderPieChart(data: ChartData<string>) {
// 渲染饼图
}
类型安全的仪表盘组件:
typescript复制interface DashboardProps {
widgets: Array<{
id: string;
type: 'chart' | 'metric' | 'table';
title: string;
data: unknown; // 具体类型根据widget类型确定
}>;
layout: {
columns: number;
rowHeight: number;
};
}
function Dashboard({ widgets, layout }: DashboardProps) {
// 实现仪表盘渲染
}
定义响应式断点类型:
typescript复制type Breakpoint = 'mobile' | 'tablet' | 'desktop';
function useBreakpoint(): Breakpoint {
// 实现断点检测逻辑
return 'desktop';
}
function ResponsiveComponent() {
const bp = useBreakpoint();
return (
<div>
{bp === 'mobile' && <MobileView />}
{bp === 'tablet' && <TableView />}
{bp === 'desktop' && <DesktopView />}
</div>
);
}
类型安全的平台检测:
typescript复制type Platform = 'ios' | 'android' | 'web';
function getPlatform(): Platform {
// 实现平台检测
return 'web';
}
const platformComponents: Record<Platform, React.ComponentType> = {
ios: IOSComponent,
android: AndroidComponent,
web: WebComponent
};
function PlatformComponent() {
const CurrentComponent = platformComponents[getPlatform()];
return <CurrentComponent />;
}
定义类型安全的Web组件:
typescript复制declare global {
namespace JSX {
interface IntrinsicElements {
'my-component': {
'count'?: number;
'onIncrement'?: (e: CustomEvent) => void;
};
}
}
}
// 使用
<my-component
count={5}
onIncrement={(e) => console.log(e.detail)}
/>
类型化的Shadow DOM操作:
typescript复制interface ShadowRootWithStyles extends ShadowRoot {
adoptedStyleSheets: CSSStyleSheet[];
}
function createComponent(): HTMLElement {
const element = document.createElement('div');
const shadow = element.attachShadow({ mode: 'open' }) as ShadowRootWithStyles;
const sheet = new CSSStyleSheet();
sheet.replaceSync(':host { display: block; }');
shadow.adoptedStyleSheets = [sheet];
return element;
}
安全的存储封装:
typescript复制type StorageKey = 'userToken' | 'darkMode' | 'lastVisited';
function getStorageItem<T>(key: StorageKey): T | null {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : null;
}
function setStorageItem<T>(key: StorageKey, value: T): void {
localStorage.setItem(key, JSON.stringify(value));
}
// 使用
const token = getStorageItem<string>('userToken');
setStorageItem('darkMode', true);
类型安全的HTTP客户端:
typescript复制async function fetchJson<T = unknown>(
input: RequestInfo,
init?: RequestInit
): Promise<T> {
const response = await fetch(input, init);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json() as Promise<T>;
}
// 使用
interface User {
id: string;
name: string;
}
const user = await fetchJson<User>('/api/user/123');
类型安全的命令行工具:
typescript复制import { Command } from 'commander';
interface CliOptions {
config?: string;
verbose?: boolean;
output?: string;
}
const program = new Command();
program
.option('-c, --config <path>', 'config file path')
.option('-v, --verbose', 'verbose output')
.option('-o, --output <dir>', 'output directory');
program.parse(process.argv);
const options = program.opts() as CliOptions;
if (options.verbose) {
console.log('Running in verbose mode');
}
类型化的文件操作:
typescript复制interface FileStats {
path: string;
size: number;
modified: Date;
isDirectory: boolean;
}
async function getFileStats(path: string): Promise<FileStats> {
const stats = await fs.promises.stat(path);
return {
path,
size: stats.size,
modified: stats.mtime,
isDirectory: stats.isDirectory()
};
}
实现泛型链表:
typescript复制class ListNode<T> {
constructor(
public value: T,
public next: ListNode<T> | null = null
) {}
}
class LinkedList<T> {
private head: ListNode<T> | null = null;
append(value: T): void {
const newNode = new ListNode(value);
if (!this.head) {
this.head = newNode;
return;
}
let current = this.head;
while (current.next) {
current = current.next;
}
current.next = newNode;
}
// 其他方法...
}
泛型快速排序实现:
typescript复制function quickSort<T>(arr: T[], compare: (a: T, b: T) => number): T[] {
if (arr.length <= 1) return arr;
const pivot = arr[0];
const left: T[] = [];
const right: T[] = [];
for (let i = 1; i < arr.length; i++) {
if (compare(arr[i], pivot) < 0) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return [...quickSort(left, compare), pivot, ...quickSort(right, compare)];
}
// 使用
const numbers = [5, 3, 8, 1, 2];
const sorted = quickSort(numbers, (a, b) => a - b);
类型安全的Canvas绘图:
typescript复制function drawCircle(
ctx: CanvasRenderingContext2D,
x: number,
y: number,
radius: number,
color: string
): void {
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill();
}
const canvas = document.getElementById('canvas') as HTMLCanvasElement;
const ctx = canvas.getContext('2d');
if (ctx) {
drawCircle(ctx, 100, 100, 50, '#ff0000');
}
类型化的WebGL程序:
typescript复制interface ShaderProgram {
program: WebGLProgram;
attributes: Record<string, number>;
uniforms: Record<string, WebGLUniformLocation | null>;
}
function createShaderProgram(
gl: WebGLRenderingContext,
vertexShaderSource: string,
fragmentShaderSource: string
): ShaderProgram {
// 实现着色器程序创建
return {
program,
attributes: {},
uniforms: {}
};
}
生成类型安全的测试数据:
typescript复制interface TestUser {
id: string;
name: string;
email: string;
age: number;
}
function createTestUser(overrides?: Partial<TestUser>): TestUser {
return {
id: 'user_' + Math.random().toString(36).substr(2, 9),
name: 'Test User',
email: 'test@example.com',
age: 25,
...overrides
};
}
// 使用
const adminUser = createTestUser({ name: 'Admin', age: 30 });
类型安全的断言库:
typescript复制interface Assert {
<T>(actual: T, expected: T, message?: string): void;
deepEqual<T>(actual: T, expected: T, message?: string): void;
throws(fn: () => void, error?: Error | string | RegExp): void;
}
const assert: Assert = {
<T>(actual: T, expected: T, message?: string) {
if (actual !== expected) {
throw new Error(message || `Expected ${expected}, got ${actual}`);
}
},
deepEqual<T>(actual: T, expected: T, message?: string) {
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
throw new Error(message || 'Deep equality check failed');
}
},
throws(fn, error) {
let threw = false;
try {
fn();
} catch (e) {
threw = true;
if (error) {
if (typeof error === 'string') {
if (e.message !== error) {
throw new Error(`Expected error message "${error}", got "${e.message}"`);
}
} else if (error instanceof RegExp) {
if (!error.test(e.message)) {
throw new Error(`Expected error message to match ${error}, got "${e.message}"`);
}
} else if (e instanceof error.constructor) {
if (e.message !== error.message) {
throw new Error(`Expected error "${error.message}", got "${e.message}"`);
}
}
}
}
if (!threw) {
throw new Error('Expected function to throw');
}
}
};
实现深度只读类型:
typescript复制type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
interface User {
name: string;
profile: {
age: number;
address: string;
};
}
const user: DeepReadonly<User> = {
name: 'Alice',
profile: {
age: 25,
address: '123 Main St'
}
};
// 以下代码会报错
// user.name = 'Bob';
// user.profile.age = 30;
实用的条件类型:
typescript复制type Nullable<T> = T | null;
type Maybe<T> = T | undefined;
type ValueOf<T> = T[keyof T];
type Promisify<T> = T extends Promise<infer U> ? T : Promise<T>;
// 使用示例
type User = { name: string; age: number };
type UserValue = ValueOf<User>; // string | number
类型安全的工厂函数:
typescript复制interface Product {
name: string;
price: number;
description(): string;
}
class Book implements Product {
constructor(
public name: string,
public price: number,
public author: string
) {}
description(): string {
return `${this.name} by ${this.author}`;
}
}
class Electronics implements Product {
constructor(
public name: string,
public price: number,
public brand: string
) {}
description(): string {
return `${this.brand} ${this.name}`;
}
}
function createProduct<T extends Product>(
type: new (...args: any[]) => T,
...args: ConstructorParameters<typeof type>
): T {
return new type(...args);
}
// 使用
const book = createProduct(Book, 'TypeScript Guide', 29.99, 'John Doe');
const laptop = createProduct(Electronics, 'Laptop', 999.99, 'Apple');
类型安全的事件系统:
typescript复制type Listener<T> = (event: T) => void;
class EventEmitter<T> {
private listeners: Listener<T>[] = [];
addListener(listener: Listener<T>): void {
this.listeners.push(listener);
}
removeListener(listener: Listener<T>): void {
this.listeners = this.listeners.filter(l => l !== listener);
}
emit(event: T): void {
for (const listener of this.listeners) {
listener(event);
}
}
}
// 使用
interface UserEvent {
userId: string;
action: 'login' | 'logout';
}
const userEvents = new EventEmitter<UserEvent>();
userEvents.addListener(event => {
console.log(`User ${event.userId} ${event.action}`);
});
userEvents.emit({ userId: '123', action: 'login' });
类型安全的柯里化:
typescript复制function curry<T1, T2, R>(fn: (a: T1, b: T2) => R): (a: T1) => (b: T2) => R;
function curry<T1, T2, T3, R>(fn: (a: T1, b: T2, c: T3) => R): (a: T1) => (b: T2) => (c: T3) => R;
function curry(fn: (...args: any[]) => any): (...args: any[]) => any {
return function curried(...args: any[]) {
if (args.length >= fn.length) {
return fn(...args);
} else {
return (...moreArgs: any[]) => curried(...args, ...moreArgs);
}
};
}
// 使用
const add = (a: number, b: number) => a + b;
const curriedAdd = curry(add);
const addFive = curriedAdd(5);
console.log(addFive(3)); // 8
类型安全的函数组合:
typescript复制function compose<T1, T2, T3>(
f: (x: T2) => T3,
g: (x: T1) => T2
): (x: T1) => T3 {
return (x) => f(g(x));
}
// 使用
const toUpperCase = (s: string) => s.toUpperCase();
const exclaim = (s: string) => s + '!';
const shout = compose(exclaim, toUpperCase);
console.log(shout('hello')); // "HELLO!"
安全地与Web Worker通信:
typescript复制// worker.ts
interface WorkerMessage<T = any> {
type: string;
payload: T;
}
self.onmessage = (e: MessageEvent<WorkerMessage>) => {
if (e.data.type === 'CALCULATE') {
const result = doCalculation(e.data.payload);
self.postMessage({
type: 'RESULT',
payload: result
});
}
};
function doCalculation(input: number): number {
return input * 2;
}
// main.ts
const worker