快速启动代码模板库
目录
WebSocket客户端
1. 基础WebSocket封装(带重连)
// utils/websocket.ts
export class WebSocketClient {
private ws: WebSocket | null = null;
private url: string;
private reconnectAttempts = 0;
private maxReconnectAttempts = 5;
private reconnectDelay = 1000;
private listeners: Map<string, Set<Function>> = new Map();
private heartbeatInterval: NodeJS.Timeout | null = null;
constructor(url: string) {
this.url = url;
}
connect(): Promise<void> {
return new Promise((resolve, reject) => {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log('WebSocket connected');
this.reconnectAttempts = 0;
this.startHeartbeat();
resolve();
};
this.ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
this.emit(data.type, data);
} catch (error) {
console.error('Failed to parse message:', error);
}
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
reject(error);
};
this.ws.onclose = () => {
console.log('WebSocket closed');
this.stopHeartbeat();
this.reconnect();
};
});
}
private reconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error('Max reconnect attempts reached');
this.emit('max_reconnect_reached', {});
return;
}
this.reconnectAttempts++;
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
console.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
setTimeout(() => {
this.connect().catch(() => {
// 失败会触发onclose,继续重连
});
}, delay);
}
private startHeartbeat() {
this.heartbeatInterval = setInterval(() => {
if (this.ws?.readyState === WebSocket.OPEN) {
this.send({ type: 'ping' });
}
}, 30000); // 30秒心跳
}
private stopHeartbeat() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
this.heartbeatInterval = null;
}
}
send(data: any) {
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(data));
} else {
console.warn('WebSocket not connected');
}
}
on(event: string, callback: Function) {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event)!.add(callback);
}
off(event: string, callback: Function) {
this.listeners.get(event)?.delete(callback);
}
private emit(event: string, data: any) {
this.listeners.get(event)?.forEach(callback => callback(data));
this.listeners.get('*')?.forEach(callback => callback(event, data));
}
disconnect() {
this.stopHeartbeat();
this.ws?.close();
this.ws = null;
}
}
使用示例:
const ws = new WebSocketClient('ws://localhost:3000');
await ws.connect();
ws.on('message', (data) => {
console.log('Received message:', data);
});
ws.on('max_reconnect_reached', () => {
alert('Connection lost. Please refresh the page.');
});
ws.send({ type: 'join_room', roomId: '123' });
2. React Hook封装
// hooks/useWebSocket.ts
import { useEffect, useState, useCallback, useRef } from 'react';
import { WebSocketClient } from '../utils/websocket';
export function useWebSocket(url: string) {
const [isConnected, setIsConnected] = useState(false);
const [lastMessage, setLastMessage] = useState<any>(null);
const wsRef = useRef<WebSocketClient | null>(null);
useEffect(() => {
const ws = new WebSocketClient(url);
wsRef.current = ws;
ws.connect().then(() => {
setIsConnected(true);
});
ws.on('*', (event: string, data: any) => {
setLastMessage({ event, data, timestamp: Date.now() });
});
ws.on('max_reconnect_reached', () => {
setIsConnected(false);
});
return () => {
ws.disconnect();
};
}, [url]);
const send = useCallback((data: any) => {
wsRef.current?.send(data);
}, []);
const subscribe = useCallback((event: string, callback: Function) => {
wsRef.current?.on(event, callback);
return () => wsRef.current?.off(event, callback);
}, []);
return { isConnected, lastMessage, send, subscribe };
}
组件中使用:
function ChatRoom({ roomId }: { roomId: string }) {
const [messages, setMessages] = useState([]);
const { isConnected, send, subscribe } = useWebSocket('ws://localhost:3000');
useEffect(() => {
const unsubscribe = subscribe('new_message', (data) => {
setMessages(prev => [...prev, data]);
});
send({ type: 'join_room', roomId });
return unsubscribe;
}, [roomId, subscribe, send]);
const handleSend = (content: string) => {
send({ type: 'message', roomId, content });
};
return (
<div>
<ConnectionStatus connected={isConnected} />
<MessageList messages={messages} />
<MessageInput onSend={handleSend} />
</div>
);
}
WebSocket服务端
1. Socket.io服务器模板
// server/websocket.ts
import { Server } from 'socket.io';
import { createServer } from 'http';
import express from 'express';
import Redis from 'ioredis';
const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer, {
cors: {
origin: process.env.CLIENT_URL,
credentials: true
},
// 配置适配器(多实例支持)
adapter: createAdapter(
new Redis(process.env.REDIS_URL),
new Redis(process.env.REDIS_URL)
)
});
// 中间件:认证
io.use(async (socket, next) => {
const token = socket.handshake.auth.token;
try {
const user = await verifyToken(token);
socket.data.user = user;
next();
} catch (error) {
next(new Error('Authentication failed'));
}
});
// 连接管理
const rooms = new Map<string, Set<string>>();
io.on('connection', (socket) => {
console.log('Client connected:', socket.id, socket.data.user);
// 加入房间
socket.on('join_room', (roomId: string) => {
socket.join(roomId);
if (!rooms.has(roomId)) {
rooms.set(roomId, new Set());
}
rooms.get(roomId)!.add(socket.id);
// 广播用户加入
io.to(roomId).emit('user_joined', {
userId: socket.data.user.id,
username: socket.data.user.name,
count: rooms.get(roomId)!.size
});
});
// 离开房间
socket.on('leave_room', (roomId: string) => {
socket.leave(roomId);
rooms.get(roomId)?.delete(socket.id);
io.to(roomId).emit('user_left', {
userId: socket.data.user.id,
count: rooms.get(roomId)?.size || 0
});
});
// 接收消息
socket.on('message', async (data) => {
const { roomId, content } = data;
// 验证权限
if (!socket.rooms.has(roomId)) {
socket.emit('error', { message: 'Not in room' });
return;
}
// 保存到数据库(异步)
const message = await saveMessage({
roomId,
userId: socket.data.user.id,
content,
timestamp: new Date()
});
// 广播消息
io.to(roomId).emit('new_message', {
id: message.id,
userId: socket.data.user.id,
username: socket.data.user.name,
content,
timestamp: message.timestamp
});
});
// 心跳
socket.on('ping', () => {
socket.emit('pong');
});
// 断开连接
socket.on('disconnect', () => {
console.log('Client disconnected:', socket.id);
// 清理房间
rooms.forEach((users, roomId) => {
if (users.has(socket.id)) {
users.delete(socket.id);
io.to(roomId).emit('user_left', {
userId: socket.data.user.id,
count: users.size
});
}
});
});
});
httpServer.listen(3000, () => {
console.log('Server running on port 3000');
});
2. 房间管理器
// server/room-manager.ts
export class RoomManager {
private rooms: Map<string, Room> = new Map();
getOrCreate(roomId: string): Room {
if (!this.rooms.has(roomId)) {
this.rooms.set(roomId, new Room(roomId));
}
return this.rooms.get(roomId)!;
}
delete(roomId: string) {
this.rooms.delete(roomId);
}
getStats() {
return {
totalRooms: this.rooms.size,
totalUsers: Array.from(this.rooms.values())
.reduce((sum, room) => sum + room.size, 0)
};
}
}
class Room {
private users: Map<string, User> = new Map();
constructor(public readonly id: string) {}
addUser(socketId: string, user: User) {
this.users.set(socketId, user);
}
removeUser(socketId: string) {
this.users.delete(socketId);
}
get size() {
return this.users.size;
}
getUsers() {
return Array.from(this.users.values());
}
}
实时数据流
1. EventSource (SSE) 客户端
// hooks/useEventSource.ts
import { useEffect, useState } from 'react';
export function useEventSource<T>(url: string) {
const [data, setData] = useState<T | null>(null);
const [isConnected, setIsConnected] = useState(false);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const eventSource = new EventSource(url);
eventSource.onopen = () => {
setIsConnected(true);
setError(null);
};
eventSource.onmessage = (event) => {
try {
const parsed = JSON.parse(event.data);
setData(parsed);
} catch (err) {
setError(err as Error);
}
};
eventSource.onerror = (err) => {
setIsConnected(false);
setError(new Error('EventSource error'));
};
return () => {
eventSource.close();
};
}, [url]);
return { data, isConnected, error };
}
使用示例:
function StockTicker() {
const { data: price, isConnected } = useEventSource<{ symbol: string, price: number }>(
'/api/stocks/AAPL/stream'
);
return (
<div>
<StatusIndicator connected={isConnected} />
{price && (
<div>
<h2>{price.symbol}</h2>
<span className="text-4xl">${price.price.toFixed(2)}</span>
</div>
)}
</div>
);
}
2. SSE服务端
// server/sse.ts
import express from 'express';
const app = express();
app.get('/api/stocks/:symbol/stream', (req, res) => {
const { symbol } = req.params;
// SSE headers
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// 发送数据函数
const send = (data: any) => {
res.write(`data: ${JSON.stringify(data)}\n\n`);
};
// 定期发送更新
const interval = setInterval(() => {
const price = getStockPrice(symbol); // 获取实时价格
send({ symbol, price, timestamp: Date.now() });
}, 1000);
// 客户端断开
req.on('close', () => {
clearInterval(interval);
res.end();
});
});
数据可视化组件
1. 实时折线图
// components/RealtimeLineChart.tsx
import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts';
import { useState, useEffect } from 'react';
interface DataPoint {
timestamp: number;
value: number;
}
export function RealtimeLineChart({ dataStream }: { dataStream: Observable<number> }) {
const [data, setData] = useState<DataPoint[]>([]);
const MAX_POINTS = 50;
useEffect(() => {
const subscription = dataStream.subscribe(value => {
setData(prev => {
const newPoint = { timestamp: Date.now(), value };
const updated = [...prev, newPoint];
return updated.slice(-MAX_POINTS); // 只保留最近50个点
});
});
return () => subscription.unsubscribe();
}, [dataStream]);
return (
<ResponsiveContainer width="100%" height={300}>
<LineChart data={data}>
<XAxis
dataKey="timestamp"
type="number"
domain={['dataMin', 'dataMax']}
tickFormatter={(timestamp) => new Date(timestamp).toLocaleTimeString()}
/>
<YAxis domain={['auto', 'auto']} />
<Tooltip
labelFormatter={(timestamp) => new Date(timestamp).toLocaleString()}
/>
<Line
type="monotone"
dataKey="value"
stroke="#8884d8"
dot={false}
isAnimationActive={false} // 禁用动画以提高性能
/>
</LineChart>
</ResponsiveContainer>
);
}
2. 实时计数器(带动画)
// components/AnimatedCounter.tsx
import { useEffect, useState } from 'react';
export function AnimatedCounter({ value, duration = 500 }: { value: number, duration?: number }) {
const [displayValue, setDisplayValue] = useState(value);
useEffect(() => {
const start = displayValue;
const end = value;
const startTime = Date.now();
const animate = () => {
const now = Date.now();
const progress = Math.min((now - startTime) / duration, 1);
// easeOutQuad
const eased = progress * (2 - progress);
const current = start + (end - start) * eased;
setDisplayValue(current);
if (progress < 1) {
requestAnimationFrame(animate);
}
};
requestAnimationFrame(animate);
}, [value]);
return (
<span className="text-4xl font-bold tabular-nums">
{Math.round(displayValue).toLocaleString()}
</span>
);
}
状态管理
1. Zustand实时状态
// store/useRealtimeStore.ts
import create from 'zustand';
import { WebSocketClient } from '../utils/websocket';
interface Message {
id: string;
content: string;
timestamp: number;
}
interface RealtimeStore {
messages: Message[];
isConnected: boolean;
ws: WebSocketClient | null;
connect: (url: string) => void;
disconnect: () => void;
sendMessage: (content: string) => void;
}
export const useRealtimeStore = create<RealtimeStore>((set, get) => ({
messages: [],
isConnected: false,
ws: null,
connect: (url: string) => {
const ws = new WebSocketClient(url);
ws.connect().then(() => {
set({ ws, isConnected: true });
});
ws.on('new_message', (message: Message) => {
set(state => ({
messages: [...state.messages, message]
}));
});
ws.on('max_reconnect_reached', () => {
set({ isConnected: false });
});
},
disconnect: () => {
const { ws } = get();
ws?.disconnect();
set({ ws: null, isConnected: false });
},
sendMessage: (content: string) => {
const { ws } = get();
ws?.send({ type: 'message', content });
}
}));
组件中使用:
function ChatApp() {
const { messages, isConnected, connect, sendMessage } = useRealtimeStore();
useEffect(() => {
connect('ws://localhost:3000');
}, [connect]);
return (
<div>
<ConnectionStatus connected={isConnected} />
{messages.map(msg => (
<MessageBubble key={msg.id} message={msg} />
))}
<MessageInput onSend={sendMessage} />
</div>
);
}
数据库操作
1. Prisma Schema
// prisma/schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id String @id @default(cuid())
email String @unique
name String
createdAt DateTime @default(now())
messages Message[]
}
model Room {
id String @id @default(cuid())
name String
createdAt DateTime @default(now())
messages Message[]
}
model Message {
id String @id @default(cuid())
content String
createdAt DateTime @default(now())
userId String
roomId String
user User @relation(fields: [userId], references: [id])
room Room @relation(fields: [roomId], references: [id])
@@index([roomId, createdAt])
}
2. Repository Pattern
// repositories/message.repository.ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export class MessageRepository {
async create(data: {
content: string;
userId: string;
roomId: string;
}) {
return await prisma.message.create({
data,
include: {
user: {
select: { id: true, name: true }
}
}
});
}
async findByRoom(roomId: string, limit = 100, cursor?: string) {
return await prisma.message.findMany({
where: { roomId },
take: limit,
skip: cursor ? 1 : 0,
cursor: cursor ? { id: cursor } : undefined,
orderBy: { createdAt: 'desc' },
include: {
user: {
select: { id: true, name: true }
}
}
});
}
async delete(id: string) {
return await prisma.message.delete({ where: { id } });
}
}
部署配置
1. Docker Compose (开发环境)
# docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
target: development
ports:
- "3000:3000"
environment:
NODE_ENV: development
DATABASE_URL: postgresql://postgres:postgres@db:5432/myapp
REDIS_URL: redis://redis:6379
volumes:
- .:/app
- /app/node_modules
depends_on:
- db
- redis
db:
image: postgres:15-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: myapp
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redisdata:/data
volumes:
pgdata:
redisdata:
2. GitHub Actions CI/CD
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build
run: npm run build
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Deploy to production
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
run: |
curl -L https://fly.io/install.sh | sh
~/.fly/bin/flyctl deploy --remote-only
3. Fly.io配置
# fly.toml
app = "my-realtime-app"
primary_region = "sjc"
[build]
[build.args]
NODE_VERSION = "18"
[env]
NODE_ENV = "production"
PORT = "8080"
[[services]]
internal_port = 8080
protocol = "tcp"
[[services.ports]]
port = 80
handlers = ["http"]
[[services.ports]]
port = 443
handlers = ["tls", "http"]
[[services.http_checks]]
interval = "10s"
timeout = "2s"
grace_period = "5s"
method = "GET"
path = "/health"
[[ services.tcp_checks ]]
interval = "10s"
timeout = "2s"
性能优化模板
1. 请求节流
// utils/throttle.ts
export function throttle<T extends (...args: any[]) => any>(
func: T,
delay: number
): (...args: Parameters<T>) => void {
let lastCall = 0;
let timeout: NodeJS.Timeout | null = null;
return function (...args: Parameters<T>) {
const now = Date.now();
if (now - lastCall < delay) {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
lastCall = now;
func(...args);
}, delay - (now - lastCall));
} else {
lastCall = now;
func(...args);
}
};
}
// 使用示例
const handleResize = throttle(() => {
console.log('Window resized');
}, 200);
window.addEventListener('resize', handleResize);
2. 虚拟滚动
// components/VirtualList.tsx
import { useVirtualizer } from '@tanstack/react-virtual';
import { useRef } from 'react';
export function VirtualList({ items }: { items: any[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 60,
overscan: 5
});
return (
<div ref={parentRef} className="h-96 overflow-auto">
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
position: 'relative'
}}
>
{virtualizer.getVirtualItems().map(virtualItem => (
<div
key={virtualItem.index}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualItem.size}px`,
transform: `translateY(${virtualItem.start}px)`
}}
>
<MessageItem message={items[virtualItem.index]} />
</div>
))}
</div>
</div>
);
}
快速开始
- 复制模板: 选择需要的代码模板
- 修改配置: 替换环境变量、URL等
- 安装依赖:
npm install - 运行测试: 确保代码正常工作
- 集成到项目: 根据需求调整
所有模板都经过测试,可以直接使用!