最近在开发一个需要处理用户动态内容的功能时,遇到了一个很有意思的场景:当用户在休息室等非正式场合进行即兴表演时,突然被外部事件打断,这种"慌乱反应"的处理逻辑其实在程序设计中很有代表性。本文将围绕这个主题,分享一套完整的动态响应系统实现方案,涵盖事件监听、状态管理、异常处理等核心环节,适合有一定前端基础的开发者学习参考。
1. 动态响应系统的核心概念
动态响应系统是指程序能够实时感知外部环境变化并做出相应反应的技术体系。在我们的场景中,系统需要监控用户的行为状态(如跳舞),并在特定事件触发时(如敲门)立即切换状态模式。
1.1 什么是状态机模式
状态机是管理程序状态转换的经典设计模式。它包含三个核心要素:状态集合、触发事件和转换规则。在我们的跳舞被打断场景中,可以定义以下状态:
- 空闲状态:用户处于待机模式
- 表演状态:用户正在跳舞或进行其他活动
- 中断状态:被外部事件打断时的临时状态
- 恢复状态:从中断中恢复后的状态
1.2 事件驱动的架构优势
采用事件驱动架构可以让系统更好地处理突发情况。当敲门事件发生时,系统不需要轮询检测,而是通过事件监听器自动触发相应的处理逻辑。这种架构的优点包括:
- 响应及时性:事件触发立即响应
- 代码解耦:不同模块通过事件通信
- 可扩展性:容易添加新的事件类型
- 资源高效:避免不必要的轮询消耗
2. 环境准备与技术选型
2.1 开发环境要求
为了实现这个动态响应系统,我们需要准备以下开发环境:
- Node.js 16.0 或更高版本
- TypeScript 4.5+ 用于类型安全
- Vue 3.0 或 React 18.0 作为前端框架
- WebSocket 支持实时通信
- Jest 用于单元测试
2.2 项目结构规划
建议采用分层架构设计,确保代码的可维护性:
src/ ├── core/ # 核心逻辑层 │ ├── stateMachine/ # 状态机实现 │ ├── eventBus/ # 事件总线 │ └── utils/ # 工具函数 ├── services/ # 服务层 │ ├── audioService/ # 音频服务 │ ├── motionService/ # 动作检测服务 │ └── notification/ # 通知服务 ├── components/ # 组件层 │ ├── DanceDetector/ # 跳舞检测组件 │ ├── InterruptionHandler/ # 中断处理组件 │ └── StatusIndicator/ # 状态指示器 └── types/ # 类型定义3. 核心状态机实现
3.1 状态机基础类设计
首先实现一个通用的状态机基类,为具体的业务状态机提供基础能力:
// src/core/stateMachine/StateMachine.ts export abstract class StateMachine<TState, TEvent> { private currentState: TState; private transitions: Map<TState, Map<TEvent, TState>> = new Map(); private stateEnterCallbacks: Map<TState, () => void> = new Map(); private stateExitCallbacks: Map<TState, () => void> = new Map(); constructor(initialState: TState) { this.currentState = initialState; } // 添加状态转换规则 protected addTransition(from: TState, event: TEvent, to: TState): void { if (!this.transitions.has(from)) { this.transitions.set(from, new Map()); } this.transitions.get(from)!.set(event, to); } // 注册状态进入回调 protected onStateEnter(state: TState, callback: () => void): void { this.stateEnterCallbacks.set(state, callback); } // 注册状态退出回调 protected onStateExit(state: TState, callback: () => void): void { this.stateExitCallbacks.set(state, callback); } // 处理事件触发状态转换 public dispatch(event: TEvent): boolean { const stateTransitions = this.transitions.get(this.currentState); if (!stateTransitions) return false; const nextState = stateTransitions.get(event); if (!nextState) return false; // 执行状态退出逻辑 const exitCallback = this.stateExitCallbacks.get(this.currentState); if (exitCallback) exitCallback(); // 执行状态进入逻辑 const enterCallback = this.stateEnterCallbacks.get(nextState); if (enterCallback) enterCallback(); this.currentState = nextState; return true; } public getCurrentState(): TState { return this.currentState; } }3.2 跳舞场景状态机实现
基于通用状态机,我们实现具体的跳舞中断场景状态机:
// src/core/stateMachine/DanceStateMachine.ts export enum DanceState { IDLE = 'idle', DANCING = 'dancing', INTERRUPTED = 'interrupted', RECOVERING = 'recovering' } export enum DanceEvent { START_DANCE = 'start_dance', DOOR_KNOCK = 'door_knock', RESUME = 'resume', FINISH = 'finish' } export class DanceStateMachine extends StateMachine<DanceState, DanceEvent> { private interruptionCount: number = 0; private lastInterruptionTime: number = 0; constructor() { super(DanceState.IDLE); this.setupTransitions(); this.setupCallbacks(); } private setupTransitions(): void { // 定义所有可能的状态转换 this.addTransition(DanceState.IDLE, DanceEvent.START_DANCE, DanceState.DANCING); this.addTransition(DanceState.DANCING, DanceEvent.DOOR_KNOCK, DanceState.INTERRUPTED); this.addTransition(DanceState.INTERRUPTED, DanceEvent.RESUME, DanceState.RECOVERING); this.addTransition(DanceState.RECOVERING, DanceEvent.FINISH, DanceState.IDLE); this.addTransition(DanceState.INTERRUPTED, DanceEvent.FINISH, DanceState.IDLE); } private setupCallbacks(): void { // 状态进入时的回调函数 this.onStateEnter(DanceState.DANCING, () => { console.log('开始跳舞模式'); this.onDanceStart(); }); this.onStateEnter(DanceState.INTERRUPTED, () => { this.interruptionCount++; this.lastInterruptionTime = Date.now(); console.log('跳舞被打断,进入慌乱状态'); this.onInterruption(); }); this.onStateEnter(DanceState.RECOVERING, () => { console.log('正在从打断中恢复'); this.onRecovery(); }); } private onDanceStart(): void { // 启动音乐播放、动作检测等 this.playBackgroundMusic(); this.startMotionDetection(); } private onInterruption(): void { // 处理打断逻辑:暂停音乐、显示提示等 this.pauseBackgroundMusic(); this.showInterruptionAlert(); this.recordInterruptionMetrics(); } private onRecovery(): void { // 恢复逻辑:渐入音乐、平滑过渡等 this.fadeInMusic(); this.smoothTransitionToDance(); } // 具体的业务方法实现 private playBackgroundMusic(): void { // 音乐播放逻辑 console.log('播放背景音乐'); } private pauseBackgroundMusic(): void { // 音乐暂停逻辑 console.log('暂停背景音乐'); } // 其他具体方法... }4. 事件监听与处理机制
4.1 事件总线实现
为了实现模块间的事件通信,我们需要一个事件总线:
// src/core/eventBus/EventBus.ts type EventCallback = (data?: any) => void; export class EventBus { private events: Map<string, EventCallback[]> = new Map(); private static instance: EventBus; public static getInstance(): EventBus { if (!EventBus.instance) { EventBus.instance = new EventBus(); } return EventBus.instance; } // 订阅事件 public on(event: string, callback: EventCallback): void { if (!this.events.has(event)) { this.events.set(event, []); } this.events.get(event)!.push(callback); } // 取消订阅 public off(event: string, callback: EventCallback): void { const callbacks = this.events.get(event); if (callbacks) { const index = callbacks.indexOf(callback); if (index > -1) { callbacks.splice(index, 1); } } } // 触发事件 public emit(event: string, data?: any): void { const callbacks = this.events.get(event); if (callbacks) { callbacks.forEach(callback => { try { callback(data); } catch (error) { console.error(`事件处理错误: ${event}`, error); } }); } } }4.2 敲门事件监听器
实现具体的敲门事件检测和处理逻辑:
// src/services/knockDetector/KnockDetector.ts export class KnockDetector { private isMonitoring: boolean = false; private knockPattern: number[] = []; // 存储敲门时间模式 private lastKnockTime: number = 0; private readonly KNOCK_THRESHOLD = 1000; // 1秒内识别为连续敲门 private readonly PATTERN_TIMEOUT = 3000; // 3秒后重置模式 constructor() { this.setupEventListeners(); } private setupEventListeners(): void { // 模拟敲门事件,实际项目中可能是音频检测或硬件输入 document.addEventListener('keydown', (event) => { if (event.code === 'Space') { this.handleKnockSignal(); } }); // 定时清理过时的敲门模式 setInterval(() => { this.cleanupOldPatterns(); }, 1000); } private handleKnockSignal(): void { const currentTime = Date.now(); // 记录敲门时间间隔 if (this.lastKnockTime > 0) { const interval = currentTime - this.lastKnockTime; this.knockPattern.push(interval); } this.lastKnockTime = currentTime; // 检测敲门模式 if (this.isValidKnockPattern()) { this.triggerKnockEvent(); this.resetPattern(); } } private isValidKnockPattern(): boolean { // 简单的敲门模式验证:2-3次连续敲门 if (this.knockPattern.length < 2) return false; const recentIntervals = this.knockPattern.slice(-2); return recentIntervals.every(interval => interval < 500); } private triggerKnockEvent(): void { const eventBus = EventBus.getInstance(); eventBus.emit('door_knock', { timestamp: Date.now(), pattern: this.knockPattern, intensity: this.calculateKnockIntensity() }); console.log('检测到敲门事件,触发状态转换'); } private calculateKnockIntensity(): number { // 根据敲门频率和模式计算强度 return this.knockPattern.length; } private cleanupOldPatterns(): void { const currentTime = Date.now(); if (currentTime - this.lastKnockTime > this.PATTERN_TIMEOUT) { this.resetPattern(); } } private resetPattern(): void { this.knockPattern = []; this.lastKnockTime = 0; } public startMonitoring(): void { this.isMonitoring = true; console.log('开始监控敲门事件'); } public stopMonitoring(): void { this.isMonitoring = false; this.resetPattern(); console.log('停止监控敲门事件'); } }5. 用户界面组件实现
5.1 状态指示器组件
使用Vue 3实现一个状态显示组件:
<!-- src/components/StatusIndicator/StatusIndicator.vue --> <template> <div class="status-indicator" :class="currentStatus"> <div class="status-icon"> <span v-html="statusIcon"></span> </div> <div class="status-text"> <h3>{{ statusTitle }}</h3> <p>{{ statusDescription }}</p> </div> <div class="interruption-count" v-if="showInterruptionCount"> 今日被打断: {{ interruptionCount }} 次 </div> </div> </template> <script setup lang="ts"> import { ref, computed, onMounted, onUnmounted } from 'vue'; import { DanceState, DanceEvent } from '../../core/stateMachine/DanceStateMachine'; import { EventBus } from '../../core/eventBus/EventBus'; const currentStatus = ref<DanceState>(DanceState.IDLE); const interruptionCount = ref(0); const eventBus = EventBus.getInstance(); const statusConfig = { [DanceState.IDLE]: { icon: '💤', title: '待机状态', description: '准备开始表演' }, [DanceState.DANCING]: { icon: '💃', title: '跳舞中', description: '正在尽情舞蹈' }, [DanceState.INTERRUPTED]: { icon: '😳', title: '被打断了', description: '有人敲门,有点慌乱' }, [DanceState.RECOVERING]: { icon: '🔄', title: '恢复中', description: '正在调整状态' } }; const statusIcon = computed(() => statusConfig[currentStatus.value]?.icon || '❓'); const statusTitle = computed(() => statusConfig[currentStatus.value]?.title || '未知状态'); const statusDescription = computed(() => statusConfig[currentStatus.value]?.description || ''); const showInterruptionCount = computed(() => interruptionCount.value > 0); const handleStateChange = (newState: DanceState) => { currentStatus.value = newState; if (newState === DanceState.INTERRUPTED) { interruptionCount.value++; } }; onMounted(() => { eventBus.on('state_change', handleStateChange); }); onUnmounted(() => { eventBus.off('state_change', handleStateChange); }); </script> <style scoped> .status-indicator { padding: 20px; border-radius: 10px; margin: 10px 0; transition: all 0.3s ease; } .status-indicator.idle { background-color: #f0f0f0; border-left: 4px solid #ccc; } .status-indicator.dancing { background-color: #e8f5e8; border-left: 4px solid #4caf50; } .status-indicator.interrupted { background-color: #ffebee; border-left: 4px solid #f44336; animation: pulse 0.5s ease-in-out; } .status-indicator.recovering { background-color: #fff3e0; border-left: 4px solid #ff9800; } .status-icon { font-size: 2em; margin-bottom: 10px; } .interruption-count { margin-top: 10px; padding: 5px 10px; background: rgba(0,0,0,0.1); border-radius: 15px; font-size: 0.9em; } @keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.05); } 100% { transform: scale(1); } } </style>5.2 跳舞检测组件
实现跳舞动作的检测和状态管理:
<!-- src/components/DanceDetector/DanceDetector.vue --> <template> <div class="dance-detector"> <video ref="videoElement" autoplay muted playsinline></video> <canvas ref="canvasElement" class="motion-canvas"></canvas> <div class="controls"> <button @click="startDetection" :disabled="isDetecting">开始检测</button> <button @click="stopDetection" :disabled="!isDetecting">停止检测</button> <button @click="simulateKnock" class="simulate-knock">模拟敲门</button> </div> <div class="metrics"> <div>动作强度: {{ motionIntensity }}%</div> <div>检测置信度: {{ confidence }}%</div> <div>持续时间: {{ duration }}秒</div> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted, onUnmounted } from 'vue'; import { EventBus } from '../../core/eventBus/EventBus'; const videoElement = ref<HTMLVideoElement>(); const canvasElement = ref<HTMLCanvasElement>(); const isDetecting = ref(false); const motionIntensity = ref(0); const confidence = ref(0); const duration = ref(0); let animationFrameId: number; let startTime: number; const eventBus = EventBus.getInstance(); const startDetection = async () => { try { const stream = await navigator.mediaDevices.getUserMedia({ video: { width: 640, height: 480 } }); if (videoElement.value) { videoElement.value.srcObject = stream; isDetecting.value = true; startTime = Date.now(); startMotionAnalysis(); // 触发开始跳舞事件 eventBus.emit('start_dance'); } } catch (error) { console.error('摄像头访问失败:', error); } }; const stopDetection = () => { isDetecting.value = false; if (animationFrameId) { cancelAnimationFrame(animationFrameId); } const stream = videoElement.value?.srcObject as MediaStream; if (stream) { stream.getTracks().forEach(track => track.stop()); } // 触发结束事件 eventBus.emit('finish_dance'); }; const simulateKnock = () => { eventBus.emit('door_knock', { simulated: true }); }; const startMotionAnalysis = () => { if (!isDetecting.value || !videoElement.value || !canvasElement.value) return; const video = videoElement.value; const canvas = canvasElement.value; const ctx = canvas.getContext('2d'); if (!ctx) return; canvas.width = video.videoWidth; canvas.height = video.videoHeight; const analyzeFrame = () => { if (!isDetecting.value) return; ctx.drawImage(video, 0, 0, canvas.width, canvas.height); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); // 简单的运动检测算法 const motionValue = calculateMotion(imageData); motionIntensity.value = Math.min(100, motionValue * 100); confidence.value = calculateConfidence(motionValue); duration.value = Math.floor((Date.now() - startTime) / 1000); // 根据运动强度判断是否在跳舞 if (motionIntensity.value > 30) { eventBus.emit('dancing_detected', { intensity: motionIntensity.value }); } animationFrameId = requestAnimationFrame(analyzeFrame); }; analyzeFrame(); }; const calculateMotion = (imageData: ImageData): number => { // 简化的运动检测实现 // 实际项目中可以使用更复杂的计算机视觉算法 let totalDiff = 0; const data = imageData.data; for (let i = 0; i < data.length; i += 4) { const brightness = (data[i] + data[i + 1] + data[i + 2]) / 3; totalDiff += Math.abs(brightness - 128); // 128是中性灰度 } return totalDiff / (imageData.width * imageData.height * 255); }; const calculateConfidence = (motionValue: number): number => { // 根据运动值计算置信度 return Math.min(100, motionValue * 200); }; onMounted(() => { duration.value = 0; }); onUnmounted(() => { stopDetection(); }); </script> <style scoped> .dance-detector { position: relative; max-width: 640px; margin: 20px auto; } video, .motion-canvas { width: 100%; height: auto; border: 2px solid #ddd; border-radius: 8px; } .motion-canvas { position: absolute; top: 0; left: 0; opacity: 0.3; } .controls { margin: 10px 0; } button { padding: 8px 16px; margin: 0 5px; border: none; border-radius: 4px; cursor: pointer; } button:disabled { opacity: 0.5; cursor: not-allowed; } .simulate-knock { background-color: #ff9800; color: white; } .metrics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-top: 10px; } .metrics div { padding: 8px; background: #f5f5f5; border-radius: 4px; text-align: center; } </style>6. 系统集成与主应用
6.1 应用入口文件
将各个模块整合到主应用中:
// src/main.ts import { createApp } from 'vue'; import App from './App.vue'; import { DanceStateMachine, DanceState, DanceEvent } from './core/stateMachine/DanceStateMachine'; import { EventBus } from './core/eventBus/EventBus'; import { KnockDetector } from './services/knockDetector/KnockDetector'; class DanceApplication { private stateMachine: DanceStateMachine; private knockDetector: KnockDetector; private eventBus: EventBus; constructor() { this.eventBus = EventBus.getInstance(); this.stateMachine = new DanceStateMachine(); this.knockDetector = new KnockDetector(); this.setupEventHandlers(); this.startApplication(); } private setupEventHandlers(): void { // 监听敲门事件 this.eventBus.on('door_knock', (data) => { console.log('收到敲门事件:', data); this.handleKnockEvent(); }); // 监听跳舞开始事件 this.eventBus.on('start_dance', () => { this.stateMachine.dispatch(DanceEvent.START_DANCE); this.updateUIState(); }); // 监听状态变化 this.eventBus.on('state_change', (newState: DanceState) => { this.updateUIState(newState); }); } private handleKnockEvent(): void { const currentState = this.stateMachine.getCurrentState(); if (currentState === DanceState.DANCING) { // 只有在跳舞状态才处理敲门事件 this.stateMachine.dispatch(DanceEvent.DOOR_KNOCK); this.showInterruptionUI(); // 3秒后自动尝试恢复 setTimeout(() => { this.attemptRecovery(); }, 3000); } } private attemptRecovery(): void { if (this.stateMachine.getCurrentState() === DanceState.INTERRUPTED) { this.stateMachine.dispatch(DanceEvent.RESUME); // 2秒后结束恢复状态 setTimeout(() => { this.stateMachine.dispatch(DanceEvent.FINISH); }, 2000); } } private showInterruptionUI(): void { // 显示打断提示UI this.eventBus.emit('show_interruption_alert', { message: '检测到外部干扰,正在调整状态...', duration: 3000 }); } private updateUIState(state?: DanceState): void { const currentState = state || this.stateMachine.getCurrentState(); this.eventBus.emit('state_change', currentState); // 更新页面标题显示当前状态 document.title = `舞蹈状态: ${this.getStateDisplayName(currentState)}`; } private getStateDisplayName(state: DanceState): string { const names = { [DanceState.IDLE]: '待机', [DanceState.DANCING]: '跳舞中', [DanceState.INTERRUPTED]: '被打断', [DanceState.RECOVERING]: '恢复中' }; return names[state] || '未知'; } private startApplication(): void { this.knockDetector.startMonitoring(); console.log('舞蹈状态监控系统已启动'); } } // 初始化应用 const app = createApp(App); const danceApp = new DanceApplication(); app.provide('danceApp', danceApp); app.mount('#app');6.2 主应用组件
<!-- src/App.vue --> <template> <div id="app"> <header class="app-header"> <h1>舞蹈状态监控系统</h1> <p>实时检测跳舞状态并处理外部干扰</p> </header> <main class="app-main"> <section class="status-section"> <StatusIndicator /> </section> <section class="detection-section"> <DanceDetector /> </section> <section class="logs-section"> <h3>事件日志</h3> <div class="log-container"> <div v-for="log in logs" :key="log.id" class="log-item" :class="log.type"> <span class="log-time">{{ log.time }}</span> <span class="log-message">{{ log.message }}</span> </div> </div> </section> </main> </div> </template> <script setup lang="ts"> import { ref, onMounted } from 'vue'; import StatusIndicator from './components/StatusIndicator/StatusIndicator.vue'; import DanceDetector from './components/DanceDetector/DanceDetector.vue'; import { EventBus } from './core/eventBus/EventBus'; interface LogEntry { id: number; time: string; message: string; type: 'info' | 'warning' | 'error'; } const logs = ref<LogEntry[]>([]); const eventBus = EventBus.getInstance(); let logId = 0; const addLog = (message: string, type: LogEntry['type'] = 'info') => { const now = new Date(); const timeString = now.toLocaleTimeString(); logs.value.unshift({ id: logId++, time: timeString, message, type }); // 限制日志数量 if (logs.value.length > 50) { logs.value = logs.value.slice(0, 50); } }; onMounted(() => { // 监听各种系统事件并记录日志 eventBus.on('state_change', (state: string) => { addLog(`状态变更: ${state}`, 'info'); }); eventBus.on('door_knock', (data: any) => { addLog(`检测到敲门事件 ${data.simulated ? '(模拟)' : ''}`, 'warning'); }); eventBus.on('dancing_detected', (data: any) => { addLog(`跳舞动作检测: 强度 ${data.intensity.toFixed(1)}%`, 'info'); }); eventBus.on('show_interruption_alert', (data: any) => { addLog(`系统提示: ${data.message}`, 'info'); }); addLog('系统初始化完成', 'info'); }); </script> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; } #app { min-height: 100vh; color: #333; } .app-header { background: rgba(255, 255, 255, 0.95); padding: 2rem; text-align: center; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } .app-header h1 { color: #2c3e50; margin-bottom: 0.5rem; } .app-header p { color: #7f8c8d; font-size: 1.1rem; } .app-main { max-width: 1200px; margin: 0 auto; padding: 2rem; display: grid; gap: 2rem; grid-template-columns: 1fr 1fr; } .status-section { grid-column: 1 / -1; } .detection-section { grid-column: 1; } .logs-section { grid-column: 2; } .log-container { background: white; border-radius: 8px; padding: 1rem; max-height: 400px; overflow-y: auto; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } .log-item { padding: 0.5rem; margin: 0.25rem 0; border-left: 4px solid #ccc; font-family: 'Courier New', monospace; font-size: 0.9rem; } .log-item.info { border-left-color: #3498db; background: #ebf5fb; } .log-item.warning { border-left-color: #f39c12; background: #fef9e7; } .log-item.error { border-left-color: #e74c3c; background: #fdedec; } .log-time { color: #7f8c8d; margin-right: 1rem; } @media (max-width: 768px) { .app-main { grid-template-columns: 1fr; padding: 1rem; } .detection-section, .logs-section { grid-column: 1; } } </style>7. 测试与验证方案
7.1 单元测试编写
为状态机核心逻辑编写测试用例:
// tests/unit/DanceStateMachine.test.ts import { DanceStateMachine, DanceState, DanceEvent } from '../../src/core/stateMachine/DanceStateMachine'; describe('DanceStateMachine', () => { let stateMachine: DanceStateMachine; beforeEach(() => { stateMachine = new DanceStateMachine(); }); test('初始状态应为空闲', () => { expect(stateMachine.getCurrentState()).toBe(DanceState.IDLE); }); test('从空闲状态开始跳舞', () => { const result = stateMachine.dispatch(DanceEvent.START_DANCE); expect(result).toBe(true); expect(stateMachine.getCurrentState()).toBe(DanceState.DANCING); }); test('跳舞时被敲门打断', () => { // 先进入跳舞状态 stateMachine.dispatch(DanceEvent.START_DANCE); // 模拟敲门事件 const result = stateMachine.dispatch(DanceEvent.DOOR_KNOCK); expect(result).toBe(true); expect(stateMachine.getCurrentState()).toBe(DanceState.INTERRUPTED); }); test无效的状态转换应返回false', () => { // 从空闲状态直接恢复应该失败 const result = stateMachine.dispatch(DanceEvent.RESUME); expect(result).toBe(false); expect(stateMachine.getCurrentState()).toBe(DanceState.IDLE); }); test('完整的流程测试', () => { // 空闲 → 跳舞 → 打断 → 恢复 → 结束 expect(stateMachine.dispatch(DanceEvent.START_DANCE)).toBe(true); expect(stateMachine.getCurrentState()).toBe(DanceState.DANCING); expect(stateMachine.dispatch(DanceEvent.DOOR_KNOCK)).toBe(true); expect(stateMachine.getCurrentState()).toBe(DanceState.INTERRUPTED); expect(stateMachine.dispatch(DanceEvent.RESUME)).toBe(true); expect(stateMachine.getCurrentState()).toBe(DanceState.RECOVERING); expect(stateMachine.dispatch(DanceEvent.FINISH)).toBe(true); expect(stateMachine.getCurrentState()).toBe(DanceState.IDLE); }); });7.2 集成测试方案
// tests/integration/ApplicationIntegration.test.ts import { DanceApplication } from '../../src/main'; import { EventBus } from '../../src/core/eventBus/EventBus'; describe('Application Integration', () => { let application: DanceApplication; let eventBus: EventBus; beforeEach(() => { eventBus = EventBus.getInstance(); application = new DanceApplication(); }); afterEach(() => { // 清理事件监听 eventBus.off('state_change'); eventBus.off('door_knock'); }); test('应用启动后应监听敲门事件', (done) => { eventBus.on('state_change', (state) => { if (state === 'interrupted') { done(); } }); // 模拟敲门事件 eventBus.emit('door_knock', { simulated: true }); }); test('状态变化应触发UI更新', (done) => { let stateChangeCount = 0; eventBus.on('state_change', (state) => { stateChangeCount++; if (stateChangeCount === 2) { // 初始状态 + 跳舞状态 expect(state).toBe('dancing'); done(); } }); eventBus.emit('start_dance'); }); });8. 性能优化与最佳实践
8.1 内存管理优化
在长时间运行的监控应用中,内存管理至关重要:
// src/utils/MemoryManager.ts export class MemoryManager { private static instance: MemoryManager; private cleanupIntervals: Map<string, number> = new Map(); private memoryUsage: number = 0; private constructor() { this.startMemoryMonitoring(); } public static getInstance(): MemoryManager { if (!MemoryManager.instance) { MemoryManager.instance = new MemoryManager(); } return MemoryManager.instance; } private startMemoryMonitoring(): void { setInterval(() => { this.checkMemoryUsage(); this.cleanupOrphanedResources(); }, 30000); // 每30秒检查一次 } private checkMemoryUsage(): void { if (typeof window !== 'undefined' && 'memory' in window.performance) { this.memoryUsage = (performance as any).memory.usedJSHeapSize; if (this.memoryUsage > 100 * 1024 * 1024) { // 100MB阈值 this.triggerCleanup(); } } } private cleanupOrphanedResources(): void { // 清理未使用的事件监听器 this.cleanupEvent