ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

状态机与事件驱动架构:构建动态响应系统的完整实践指南

状态机与事件驱动架构:构建动态响应系统的完整实践指南 最近在开发一个需要处理用户动态内容的功能时遇到了一个很有意思的场景当用户在休息室等非正式场合进行即兴表演时突然被外部事件打断这种慌乱反应的处理逻辑其实在程序设计中很有代表性。本文将围绕这个主题分享一套完整的动态响应系统实现方案涵盖事件监听、状态管理、异常处理等核心环节适合有一定前端基础的开发者学习参考。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 StateMachineTState, TEvent { private currentState: TState; private transitions: MapTState, MapTEvent, TState new Map(); private stateEnterCallbacks: MapTState, () void new Map(); private stateExitCallbacks: MapTState, () 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 StateMachineDanceState, 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: Mapstring, 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 classstatus-indicator :classcurrentStatus div classstatus-icon span v-htmlstatusIcon/span /div div classstatus-text h3{{ statusTitle }}/h3 p{{ statusDescription }}/p /div div classinterruption-count v-ifshowInterruptionCount 今日被打断: {{ interruptionCount }} 次 /div /div /template script setup langts import { ref, computed, onMounted, onUnmounted } from vue; import { DanceState, DanceEvent } from ../../core/stateMachine/DanceStateMachine; import { EventBus } from ../../core/eventBus/EventBus; const currentStatus refDanceState(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); } } /style5.2 跳舞检测组件实现跳舞动作的检测和状态管理!-- src/components/DanceDetector/DanceDetector.vue -- template div classdance-detector video refvideoElement autoplay muted playsinline/video canvas refcanvasElement classmotion-canvas/canvas div classcontrols button clickstartDetection :disabledisDetecting开始检测/button button clickstopDetection :disabled!isDetecting停止检测/button button clicksimulateKnock classsimulate-knock模拟敲门/button /div div classmetrics div动作强度: {{ motionIntensity }}%/div div检测置信度: {{ confidence }}%/div div持续时间: {{ duration }}秒/div /div /div /template script setup langts import { ref, onMounted, onUnmounted } from vue; import { EventBus } from ../../core/eventBus/EventBus; const videoElement refHTMLVideoElement(); const canvasElement refHTMLCanvasElement(); 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; } /style6. 系统集成与主应用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 idapp header classapp-header h1舞蹈状态监控系统/h1 p实时检测跳舞状态并处理外部干扰/p /header main classapp-main section classstatus-section StatusIndicator / /section section classdetection-section DanceDetector / /section section classlogs-section h3事件日志/h3 div classlog-container div v-forlog in logs :keylog.id classlog-item :classlog.type span classlog-time{{ log.time }}/span span classlog-message{{ log.message }}/span /div /div /section /main /div /template script setup langts 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 refLogEntry[]([]); 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; } } /style7. 测试与验证方案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: Mapstring, 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
返回列表