兰 亭 墨 苑
期货 · 量化 · AI · 终身学习
首页
归档
编辑文章
标题 *
URL 别名 *
内容 *
(支持 Markdown 格式)
# JSBox 事件驱动架构:优雅处理界面更新 在复杂的 JSBox 应用中,界面更新往往涉及多个组件之间的协调。事件驱动架构提供了一种优雅的解决方案,让 UI 更新变得更加可控、可维护。让我们深入探讨如何构建一个强大的事件驱动 UI 系统。 ## 一、为什么需要事件驱动的 UI 架构 ### 1. 传统方式的痛点 在没有事件驱动架构的情况下,UI 更新通常是直接调用: ```javascript // 传统方式:直接更新UI function updateUserInfo(user) { $("userName").text = user.name; $("userAvatar").src = user.avatar; $("userScore").text = user.score; // 如果有多处需要更新... updateSidebar(user); updateHeader(user); refreshStatistics(user); // 代码耦合严重,难以维护 } ``` 这种方式存在以下问题: - **耦合度高**:业务逻辑与 UI 更新混杂 - **难以扩展**:添加新的 UI 组件需要修改多处代码 - **状态同步困难**:多个组件显示同一数据时容易不一致 - **调试困难**:难以追踪 UI 更新的来源和时机 ### 2. 事件驱动的优势 事件驱动架构通过发布-订阅模式解耦了数据变化和 UI 更新: ```javascript // 事件驱动方式 eventBus.emit('user:updated', user); // UI组件自行决定如何响应这个事件 ``` 优势包括: - **松耦合**:数据层不需要知道有哪些 UI 需要更新 - **易扩展**:新增 UI 组件只需订阅相应事件 - **统一管理**:所有 UI 更新通过事件流转,便于监控和调试 - **异步友好**:天然支持异步更新,避免阻塞 ## 二、构建 UI 事件系统 ### 1. 核心事件总线 首先,我们需要一个强大的事件总线作为整个系统的核心: ```javascript class UIEventBus { constructor() { this.events = new Map(); this.eventHistory = []; this.maxHistorySize = 100; this.debug = false; } // 订阅事件 on(eventName, handler, options = {}) { const { priority = 0, // 优先级 once = false, // 是否只执行一次 context = null // 执行上下文 } = options; if (!this.events.has(eventName)) { this.events.set(eventName, []); } const wrapper = { handler, priority, once, context, id: Date.now() + Math.random() }; // 按优先级插入 const handlers = this.events.get(eventName); const insertIndex = handlers.findIndex(h => h.priority < priority); if (insertIndex === -1) { handlers.push(wrapper); } else { handlers.splice(insertIndex, 0, wrapper); } // 返回取消订阅函数 return () => this.off(eventName, wrapper.id); } // 取消订阅 off(eventName, handlerId) { if (!this.events.has(eventName)) return; const handlers = this.events.get(eventName); const index = handlers.findIndex(h => h.id === handlerId); if (index !== -1) { handlers.splice(index, 1); } // 清理空数组 if (handlers.length === 0) { this.events.delete(eventName); } } // 发射事件 emit(eventName, data) { // 记录事件历史 if (this.debug) { this.recordEvent(eventName, data); } if (!this.events.has(eventName)) return; const handlers = [...this.events.get(eventName)]; handlers.forEach(wrapper => { try { // 在指定上下文中执行 const result = wrapper.context ? wrapper.handler.call(wrapper.context, data) : wrapper.handler(data); // 处理异步handler if (result instanceof Promise) { result.catch(error => { console.error(`Event handler error for ${eventName}:`, error); }); } // 如果是一次性事件,执行后移除 if (wrapper.once) { this.off(eventName, wrapper.id); } } catch (error) { console.error(`Event handler error for ${eventName}:`, error); } }); } // 异步发射事件(等待所有handler完成) async emitAsync(eventName, data) { if (!this.events.has(eventName)) return; const handlers = [...this.events.get(eventName)]; for (const wrapper of handlers) { try { await Promise.resolve( wrapper.context ? wrapper.handler.call(wrapper.context, data) : wrapper.handler(data) ); if (wrapper.once) { this.off(eventName, wrapper.id); } } catch (error) { console.error(`Async event handler error for ${eventName}:`, error); } } } // 记录事件历史(用于调试) recordEvent(eventName, data) { this.eventHistory.push({ eventName, data: JSON.parse(JSON.stringify(data || {})), timestamp: Date.now(), handlers: this.events.get(eventName)?.length || 0 }); // 限制历史记录大小 if (this.eventHistory.length > this.maxHistorySize) { this.eventHistory.shift(); } } // 获取事件历史 getEventHistory(eventName = null) { if (eventName) { return this.eventHistory.filter(e => e.eventName === eventName); } return [...this.eventHistory]; } } ``` ### 2. UI 组件基类 为了让 UI 组件更好地集成事件系统,我们创建一个基类: ```javascript class UIComponent { constructor(id, eventBus) { this.id = id; this.eventBus = eventBus; this.subscriptions = []; this.state = {}; this.initialized = false; } // 订阅事件的便捷方法 subscribe(eventName, handler, options = {}) { const unsubscribe = this.eventBus.on(eventName, handler.bind(this), { ...options, context: this }); this.subscriptions.push(unsubscribe); return unsubscribe; } // 发射事件的便捷方法 emit(eventName, data) { this.eventBus.emit(eventName, { ...data, source: this.id, timestamp: Date.now() }); } // 更新组件状态 setState(updates) { const oldState = { ...this.state }; this.state = { ...this.state, ...updates }; // 触发状态变化事件 this.emit('component:stateChanged', { componentId: this.id, oldState, newState: this.state, changes: updates }); // 触发UI更新 this.render(); } // 批量更新状态 batchUpdate(updater) { const updates = {}; const proxy = new Proxy(updates, { set: (target, prop, value) => { target[prop] = value; return true; } }); updater(proxy); this.setState(updates); } // 渲染UI(子类实现) render() { // 子类覆盖此方法 } // 销毁组件 destroy() { // 取消所有事件订阅 this.subscriptions.forEach(unsubscribe => unsubscribe()); this.subscriptions = []; // 发射销毁事件 this.emit('component:destroyed', { componentId: this.id }); } } ``` ### 3. 实际 UI 组件示例 让我们创建一些实际的 UI 组件来展示如何使用这个系统: ```javascript // 用户信息组件 class UserInfoComponent extends UIComponent { constructor(eventBus) { super('userInfo', eventBus); this.setupEventListeners(); this.createUI(); } setupEventListeners() { // 监听用户数据更新 this.subscribe('user:updated', (data) => { this.setState({ user: data.user }); }); // 监听用户登出 this.subscribe('user:logout', () => { this.setState({ user: null }); }); } createUI() { $ui.render({ views: [{ type: "view", props: { id: "userInfoContainer" }, layout: $layout.fill, views: [ { type: "image", props: { id: "userAvatar", radius: 25 }, layout: (make, view) => { make.left.top.inset(10); make.size.equalTo($size(50, 50)); } }, { type: "label", props: { id: "userName", font: $font("bold", 16) }, layout: (make, view) => { make.left.equalTo($("userAvatar").right).offset(10); make.top.equalTo($("userAvatar")); } }, { type: "label", props: { id: "userScore", font: $font(14), textColor: $color("gray") }, layout: (make, view) => { make.left.equalTo($("userName")); make.top.equalTo($("userName").bottom).offset(5); } } ] }] }); } render() { const { user } = this.state; if (user) { $("userAvatar").src = user.avatar; $("userName").text = user.name; $("userScore").text = `积分: ${user.score}`; $("userInfoContainer").hidden = false; } else { $("userInfoContainer").hidden = true; } } } // 消息列表组件 class MessageListComponent extends UIComponent { constructor(eventBus) { super('messageList', eventBus); this.state = { messages: [], filter: 'all' }; this.setupEventListeners(); this.createUI(); } setupEventListeners() { // 新消息 this.subscribe('message:new', (data) => { this.addMessage(data.message); }); // 删除消息 this.subscribe('message:delete', (data) => { this.removeMessage(data.messageId); }); // 标记已读 this.subscribe('message:markRead', (data) => { this.markAsRead(data.messageId); }); // 过滤器变化 this.subscribe('filter:changed', (data) => { this.setState({ filter: data.filter }); }); } addMessage(message) { this.setState({ messages: [message, ...this.state.messages] }); // 发射消息数量变化事件 this.emit('messages:countChanged', { count: this.state.messages.length, unreadCount: this.getUnreadCount() }); } removeMessage(messageId) { this.setState({ messages: this.state.messages.filter(m => m.id !== messageId) }); } markAsRead(messageId) { const messages = this.state.messages.map(m => m.id === messageId ? { ...m, read: true } : m ); this.setState({ messages }); } getUnreadCount() { return this.state.messages.filter(m => !m.read).length; } getFilteredMessages() { const { messages, filter } = this.state; switch (filter) { case 'unread': return messages.filter(m => !m.read); case 'starred': return messages.filter(m => m.starred); default: return messages; } } createUI() { $ui.render({ views: [{ type: "list", props: { id: "messageList", rowHeight: 80, template: { views: [ { type: "label", props: { id: "title", font: $font("bold", 16) }, layout: (make, view) => { make.left.top.inset(10); make.right.inset(50); } }, { type: "label", props: { id: "content", font: $font(14), lines: 2 }, layout: (make, view) => { make.left.right.equalTo($("title")); make.top.equalTo($("title").bottom).offset(5); } }, { type: "view", props: { id: "unreadDot", bgcolor: $color("red"), circular: true }, layout: (make, view) => { make.right.inset(15); make.centerY.equalTo(view.super); make.size.equalTo($size(8, 8)); } } ] }, actions: [ { title: "删除", color: $color("red"), handler: (sender, indexPath) => { const message = this.getFilteredMessages()[indexPath.row]; this.emit('message:delete', { messageId: message.id }); } }, { title: "标记已读", handler: (sender, indexPath) => { const message = this.getFilteredMessages()[indexPath.row]; this.emit('message:markRead', { messageId: message.id }); } } ] }, layout: $layout.fill, events: { didSelect: (sender, indexPath, data) => { const message = this.getFilteredMessages()[indexPath.row]; this.emit('message:selected', { message }); } } }] }); } render() { const filteredMessages = this.getFilteredMessages(); $("messageList").data = filteredMessages.map(message => ({ title: { text: message.title }, content: { text: message.content }, unreadDot: { hidden: message.read } })); } } ``` ## 三、高级 UI 更新模式 ### 1. 状态管理器 为了更好地管理应用状态,我们创建一个中央状态管理器: ```javascript class StateManager { constructor(eventBus) { this.eventBus = eventBus; this.state = {}; this.computedValues = new Map(); this.watchers = new Map(); } // 设置状态 setState(path, value) { const oldValue = this.getState(path); // 使用路径设置嵌套值 const keys = path.split('.'); let current = this.state; for (let i = 0; i < keys.length - 1; i++) { if (!current[keys[i]]) { current[keys[i]] = {}; } current = current[keys[i]]; } current[keys[keys.length - 1]] = value; // 触发状态变化事件 this.eventBus.emit('state:changed', { path, oldValue, newValue: value }); // 执行相关的watchers this.executeWatchers(path); // 更新计算属性 this.updateComputedValues(); } // 获取状态 getState(path) { const keys = path.split('.'); let current = this.state; for (const key of keys) { if (current[key] === undefined) { return undefined; } current = current[key]; } return current; } // 监听状态变化 watch(path, handler) { if (!this.watchers.has(path)) { this.watchers.set(path, []); } this.watchers.get(path).push(handler); // 返回取消监听函数 return () => { const handlers = this.watchers.get(path); const index = handlers.indexOf(handler); if (index > -1) { handlers.splice(index, 1); } }; } // 定义计算属性 computed(name, getter) { this.computedValues.set(name, { getter, cache: null, dirty: true }); } // 获取计算属性值 getComputed(name) { const computed = this.computedValues.get(name); if (!computed) return undefined; if (computed.dirty) { computed.cache = computed.getter(this.state); computed.dirty = false; } return computed.cache; } // 执行watchers executeWatchers(path) { // 执行精确匹配的watchers const exactWatchers = this.watchers.get(path) || []; exactWatchers.forEach(handler => handler(this.getState(path))); // 执行父路径的watchers const keys = path.split('.'); for (let i = keys.length - 1; i > 0; i--) { const parentPath = keys.slice(0, i).join('.'); const parentWatchers = this.watchers.get(parentPath) || []; parentWatchers.forEach(handler => handler(this.getState(parentPath))); } } // 更新所有计算属性 updateComputedValues() { this.computedValues.forEach(computed => { computed.dirty = true; }); } } // 使用示例 const stateManager = new StateManager(eventBus); // 定义计算属性 stateManager.computed('totalUnread', (state) => { return (state.messages || []).filter(m => !m.read).length; }); stateManager.computed('activeUserName', (state) => { return state.user ? state.user.name : '未登录'; }); // 监听状态变化 stateManager.watch('user', (user) => { if (user) { eventBus.emit('user:updated', { user }); } else { eventBus.emit('user:logout'); } }); // 在组件中使用 class HeaderComponent extends UIComponent { constructor(eventBus, stateManager) { super('header', eventBus); this.stateManager = stateManager; this.setupWatchers(); } setupWatchers() { // 监听计算属性变化 this.stateManager.watch('messages', () => { const unreadCount = this.stateManager.getComputed('totalUnread'); this.updateBadge(unreadCount); }); } updateBadge(count) { $("messageBadge").text = count > 0 ? String(count) : ""; $("messageBadge").hidden = count === 0; } } ``` ### 2. UI 更新队列 为了优化性能,我们实现一个 UI 更新队列,批量处理更新: ```javascript class UIUpdateQueue { constructor() { this.queue = []; this.isProcessing = false; this.batchDelay = 16; // 约等于60fps } // 添加更新任务 enqueue(component, updateFn, priority = 0) { const task = { component, updateFn, priority, timestamp: Date.now() }; // 按优先级插入 const insertIndex = this.queue.findIndex(t => t.priority < priority); if (insertIndex === -1) { this.queue.push(task); } else { this.queue.splice(insertIndex, 0, task); } // 开始处理队列 if (!this.isProcessing) { this.scheduleProcessing(); } } // 调度处理 scheduleProcessing() { this.isProcessing = true; // 使用requestAnimationFrame或setTimeout $delay(this.batchDelay / 1000, () => { this.processQueue(); }); } // 处理队列 processQueue() { const startTime = Date.now(); const timeLimit = 16; // 单帧时间限制 while (this.queue.length > 0) { // 检查时间限制 if (Date.now() - startTime > timeLimit) { // 超时,下一帧继续 this.scheduleProcessing(); return; } const task = this.queue.shift(); try { task.updateFn.call(task.component); } catch (error) { console.error(`UI update error in ${task.component.id}:`, error); } } this.isProcessing = false; } // 清空队列 clear() { this.queue = []; this.isProcessing = false; } } // 全局更新队列 const uiUpdateQueue = new UIUpdateQueue(); // 修改UIComponent基类,使用更新队列 class UIComponentWithQueue extends UIComponent { render() { // 将更新加入队列而不是立即执行 uiUpdateQueue.enqueue(this, this._render, this.renderPriority || 0); } _render() { // 实际的渲染逻辑 } // 立即渲染(跳过队列) forceRender() { this._render(); } } ``` ### 3. 虚拟 DOM 实现 对于复杂的列表更新,我们可以实现一个简单的虚拟 DOM: ```javascript class VirtualDOM { constructor() { this.currentTree = null; } // 创建虚拟节点 createElement(type, props, children) { return { type, props: props || {}, children: children || [] }; } // 比较两个虚拟节点 diff(oldNode, newNode) { const patches = []; if (!oldNode) { patches.push({ type: 'CREATE', newNode }); } else if (!newNode) { patches.push({ type: 'REMOVE' }); } else if (oldNode.type !== newNode.type) { patches.push({ type: 'REPLACE', newNode }); } else { // 比较属性 const propPatches = this.diffProps(oldNode.props, newNode.props); if (propPatches.length > 0) { patches.push({ type: 'UPDATE_PROPS', props: propPatches }); } // 比较子节点 const childPatches = this.diffChildren(oldNode.children, newNode.children); if (childPatches.length > 0) { patches.push({ type: 'UPDATE_CHILDREN', children: childPatches }); } } return patches; } // 比较属性 diffProps(oldProps, newProps) { const patches = []; const allKeys = new Set([...Object.keys(oldProps), ...Object.keys(newProps)]); allKeys.forEach(key => { if (oldProps[key] !== newProps[key]) { patches.push({ key, value: newProps[key] }); } }); return patches; } // 比较子节点 diffChildren(oldChildren, newChildren) { const patches = []; const maxLength = Math.max(oldChildren.length, newChildren.length); for (let i = 0; i < maxLength; i++) { const childPatches = this.diff(oldChildren[i], newChildren[i]); if (childPatches.length > 0) { patches.push({ index: i, patches: childPatches }); } } return patches; } // 应用补丁到真实DOM applyPatches(element, patches) { patches.forEach(patch => { switch (patch.type) { case 'UPDATE_PROPS': patch.props.forEach(prop => { element.props[prop.key] = prop.value; }); break; case 'UPDATE_CHILDREN': // 递归应用子节点补丁 patch.children.forEach(child => { const childElement = element.views[child.index]; if (childElement) { this.applyPatches(childElement, child.patches); } }); break; // 其他补丁类型... } }); } } // 在列表组件中使用虚拟DOM class OptimizedListComponent extends UIComponent { constructor(eventBus) { super('optimizedList', eventBus); this.virtualDOM = new VirtualDOM(); this.items = []; } updateItems(newItems) { const oldTree = this.createVirtualTree(this.items); const newTree = this.createVirtualTree(newItems); const patches = this.virtualDOM.diff(oldTree, newTree); // 只更新变化的部分 this.applyListPatches(patches); this.items = newItems; } createVirtualTree(items) { return this.virtualDOM.createElement('list', {}, items.map(item => this.virtualDOM.createElement('item', { id: item.id, title: item.title, selected: item.selected }) ) ); } } ``` ## 四、实战案例:聊天应用 让我们通过一个完整的聊天应用示例,展示事件驱动架构的威力: ```javascript // 聊天应用主类 class ChatApp { constructor() { this.eventBus = new UIEventBus(); this.stateManager = new StateManager(this.eventBus); this.components = new Map(); this.initializeState(); this.createComponents(); this.setupEventHandlers(); } initializeState() { // 初始化应用状态 this.stateManager.setState('currentUser', null); this.stateManager.setState('conversations', []); this.stateManager.setState('activeConversation', null); this.stateManager.setState('messages', {}); this.stateManager.setState('typing', {}); } createComponents() { // 创建各个UI组件 this.components.set('sidebar', new ConversationListComponent(this.eventBus, this.stateManager)); this.components.set('chatView', new ChatViewComponent(this.eventBus, this.stateManager)); this.components.set('inputBar', new MessageInputComponent(this.eventBus, this.stateManager)); this.components.set('header', new ChatHeaderComponent(this.eventBus, this.stateManager)); } setupEventHandlers() { // WebSocket连接 this.websocket = new WebSocketManager(this.eventBus); // 处理接收到的消息 this.eventBus.on('ws:message', (data) => { const { type, payload } = data; switch (type) { case 'new_message': this.handleNewMessage(payload); break; case 'user_typing': this.handleUserTyping(payload); break; case 'user_online': this.handleUserOnline(payload); break; } }); } handleNewMessage(message) { const conversationId = message.conversationId; const messages = this.stateManager.getState(`messages.${conversationId}`) || []; // 添加新消息 messages.push(message); this.stateManager.setState(`messages.${conversationId}`, messages); // 更新会话最后消息 const conversations = this.stateManager.getState('conversations'); const updatedConversations = conversations.map(conv => conv.id === conversationId ? { ...conv, lastMessage: message, unreadCount: conv.unreadCount + 1 } : conv ); this.stateManager.setState('conversations', updatedConversations); // 发送通知 if (message.userId !== this.stateManager.getState('currentUser.id')) { this.showNotification(message); } } } // 会话列表组件 class ConversationListComponent extends UIComponent { constructor(eventBus, stateManager) { super('conversationList', eventBus); this.stateManager = stateManager; this.setupUI(); this.setupEventListeners(); } setupEventListeners() { // 监听会话列表变化 this.stateManager.watch('conversations', (conversations) => { this.updateConversationList(conversations); }); // 监听活动会话变化 this.stateManager.watch('activeConversation', (conversationId) => { this.highlightActiveConversation(conversationId); }); } setupUI() { // 创建会话列表UI $ui.push({ views: [{ type: "list", props: { id: "conversationList", rowHeight: 70, template: { views: [ { type: "image", props: { id: "avatar", radius: 20 }, layout: (make) => { make.left.inset(10); make.centerY.equalTo(); make.size.equalTo($size(40, 40)); } }, { type: "label", props: { id: "name", font: $font("bold", 16) }, layout: (make) => { make.left.equalTo($("avatar").right).offset(10); make.top.inset(10); make.right.inset(60); } }, { type: "label", props: { id: "lastMessage", font: $font(14), textColor: $color("gray") }, layout: (make) => { make.left.equalTo($("name")); make.top.equalTo($("name").bottom).offset(5); make.right.inset(60); } }, { type: "label", props: { id: "time", font: $font(12), textColor: $color("gray"), align: $align.right }, layout: (make) => { make.right.inset(10); make.top.inset(10); } }, { type: "label", props: { id: "badge", font: $font(12), textColor: $color("white"), bgcolor: $color("red"), circular: true, align: $align.center }, layout: (make) => { make.right.inset(10); make.bottom.inset(10); make.size.equalTo($size(20, 20)); } } ] } }, layout: $layout.fill, events: { didSelect: (sender, indexPath, data) => { const conversation = this.conversations[indexPath.row]; this.emit('conversation:selected', { conversationId: conversation.id }); } } }] }); } updateConversationList(conversations) { this.conversations = conversations; $("conversationList").data = conversations.map(conv => ({ avatar: { src: conv.avatar }, name: { text: conv.name }, lastMessage: { text: conv.lastMessage?.text || "" }, time: { text: this.formatTime(conv.lastMessage?.timestamp) }, badge: { text: conv.unreadCount > 0 ? String(conv.unreadCount) : "", hidden: conv.unreadCount === 0 } })); } formatTime(timestamp) { if (!timestamp) return ""; const now = Date.now(); const diff = now - timestamp; if (diff < 60000) return "刚刚"; if (diff < 3600000) return Math.floor(diff / 60000) + "分钟前"; if (diff < 86400000) return Math.floor(diff / 3600000) + "小时前"; return new Date(timestamp).toLocaleDateString(); } } // 聊天视图组件 class ChatViewComponent extends UIComponent { constructor(eventBus, stateManager) { super('chatView', eventBus); this.stateManager = stateManager; this.messageCache = new Map(); this.setupUI(); this.setupEventListeners(); } setupEventListeners() { // 监听活动会话变化 this.stateManager.watch('activeConversation', (conversationId) => { if (conversationId) { this.loadMessages(conversationId); } }); // 监听新消息 this.eventBus.on('message:new', (data) => { if (data.conversationId === this.stateManager.getState('activeConversation')) { this.appendMessage(data.message); } }); // 监听输入状态 this.stateManager.watch('typing', (typingUsers) => { this.updateTypingIndicator(typingUsers); }); } appendMessage(message) { // 使用虚拟滚动优化大量消息 const messageElement = this.createMessageElement(message); // 添加动画效果 messageElement.alpha = 0; $("messageContainer").add(messageElement); $ui.animate({ duration: 0.3, animation: () => { messageElement.alpha = 1; }, completion: () => { // 滚动到底部 this.scrollToBottom(); } }); } createMessageElement(message) { const isCurrentUser = message.userId === this.stateManager.getState('currentUser.id'); return { type: "view", props: { id: `message_${message.id}` }, layout: (make) => { make.left.right.inset(10); make.height.equalTo(80); }, views: [ { type: "view", props: { bgcolor: isCurrentUser ? $color("#007AFF") : $color("#E5E5EA"), radius: 16 }, layout: (make) => { if (isCurrentUser) { make.right.inset(0); } else { make.left.inset(0); } make.top.bottom.inset(5); make.width.lessThanOrEqualTo(250); }, views: [ { type: "label", props: { text: message.text, textColor: isCurrentUser ? $color("white") : $color("black"), lines: 0 }, layout: (make) => { make.edges.inset(10); } } ] } ] }; } } // WebSocket管理器 class WebSocketManager { constructor(eventBus) { this.eventBus = eventBus; this.ws = null; this.reconnectAttempts = 0; this.messageQueue = []; this.connect(); } connect() { this.ws = new WebSocket('wss://chat.example.com'); this.ws.onopen = () => { console.log('WebSocket connected'); this.reconnectAttempts = 0; // 发送队列中的消息 this.flushMessageQueue(); this.eventBus.emit('ws:connected'); }; this.ws.onmessage = (event) => { const data = JSON.parse(event.data); this.eventBus.emit('ws:message', data); }; this.ws.onclose = () => { console.log('WebSocket disconnected'); this.eventBus.emit('ws:disconnected'); // 自动重连 this.scheduleReconnect(); }; this.ws.onerror = (error) => { console.error('WebSocket error:', error); this.eventBus.emit('ws:error', error); }; } send(data) { if (this.ws.readyState === WebSocket.OPEN) { this.ws.send(JSON.stringify(data)); } else { // 加入队列等待连接 this.messageQueue.push(data); } } flushMessageQueue() { while (this.messageQueue.length > 0) { const message = this.messageQueue.shift(); this.send(message); } } scheduleReconnect() { const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000); this.reconnectAttempts++; setTimeout(() => { console.log(`Attempting to reconnect... (attempt ${this.reconnectAttempts})`); this.connect(); }, delay); } } ``` ## 五、性能优化技巧 ### 1. 事件节流和防抖 对于频繁触发的事件,使用节流和防抖优化性能: ```javascript class EventOptimizer { constructor(eventBus) { this.eventBus = eventBus; this.throttleTimers = new Map(); this.debounceTimers = new Map(); } // 节流 throttle(eventName, handler, delay = 100) { return this.eventBus.on(eventName, (data) => { const key = `${eventName}_throttle`; if (!this.throttleTimers.has(key)) { handler(data); this.throttleTimers.set(key, setTimeout(() => { this.throttleTimers.delete(key); }, delay)); } }); } // 防抖 debounce(eventName, handler, delay = 300) { return this.eventBus.on(eventName, (data) => { const key = `${eventName}_debounce`; if (this.debounceTimers.has(key)) { clearTimeout(this.debounceTimers.get(key)); } this.debounceTimers.set(key, setTimeout(() => { handler(data); this.debounceTimers.delete(key); }, delay)); }); } } // 使用示例 const optimizer = new EventOptimizer(eventBus); // 搜索输入防抖 optimizer.debounce('search:input', async (data) => { const results = await searchAPI(data.query); eventBus.emit('search:results', { results }); }, 500); // 滚动事件节流 optimizer.throttle('scroll:update', (data) => { updateScrollPosition(data.position); }, 100); ``` ### 2. 事件委托 对于大量相似元素的事件处理,使用事件委托: ```javascript class EventDelegator { constructor(container, eventBus) { this.container = container; this.eventBus = eventBus; this.handlers = new Map(); this.setupDelegation(); } setupDelegation() { // 委托点击事件 this.container.addEventListener('click', (event) => { this.handleEvent('click', event); }); // 委托其他事件... } handleEvent(type, event) { let target = event.target; // 向上查找匹配的元素 while (target && target !== this.container) { const handlers = this.handlers.get(`${type}:${target.className}`); if (handlers) { handlers.forEach(handler => { handler.call(target, event); }); if (!event.defaultPrevented) { event.stopPropagation(); } } target = target.parentElement; } } on(selector, eventType, handler) { const key = `${eventType}:${selector}`; if (!this.handlers.has(key)) { this.handlers.set(key, []); } this.handlers.get(key).push(handler); } } // 使用示例 const delegator = new EventDelegator($("listContainer"), eventBus); // 为所有列表项添加点击处理 delegator.on('list-item', 'click', function(event) { const itemId = this.getAttribute('data-id'); eventBus.emit('item:selected', { itemId }); }); ``` ### 3. 懒加载和虚拟滚动 对于长列表,实现虚拟滚动以优化性能: ```javascript class VirtualScroller { constructor(container, eventBus, options = {}) { this.container = container; this.eventBus = eventBus; this.itemHeight = options.itemHeight || 50; this.buffer = options.buffer || 5; this.items = []; this.visibleRange = { start: 0, end: 0 }; this.setupScrollListener(); } setItems(items) { this.items = items; this.updateView(); } setupScrollListener() { let scrollTimer; this.container.addEventListener('scroll', () => { clearTimeout(scrollTimer); scrollTimer = setTimeout(() => { this.updateView(); }, 10); }); } updateView() { const scrollTop = this.container.scrollTop; const containerHeight = this.container.clientHeight; // 计算可见范围 const start = Math.max(0, Math.floor(scrollTop / this.itemHeight) - this.buffer); const end = Math.min( this.items.length, Math.ceil((scrollTop + containerHeight) / this.itemHeight) + this.buffer ); // 只在范围变化时更新 if (start !== this.visibleRange.start || end !== this.visibleRange.end) { this.visibleRange = { start, end }; this.renderVisibleItems(); } } renderVisibleItems() { const visibleItems = this.items.slice( this.visibleRange.start, this.visibleRange.end ); // 发射更新事件 this.eventBus.emit('virtualScroll:update', { visibleItems, range: this.visibleRange, totalHeight: this.items.length * this.itemHeight }); } } ``` ## 六、调试和监控 ### 1. 事件追踪器 创建一个事件追踪器来监控和调试事件流: ```javascript class EventTracker { constructor(eventBus) { this.eventBus = eventBus; this.tracking = false; this.events = []; this.filters = new Set(); } start() { this.tracking = true; // 拦截所有事件 const originalEmit = this.eventBus.emit; this.eventBus.emit = (eventName, data) => { if (this.tracking && this.shouldTrack(eventName)) { this.recordEvent(eventName, data); } return originalEmit.call(this.eventBus, eventName, data); }; } stop() { this.tracking = false; } shouldTrack(eventName) { if (this.filters.size === 0) return true; return this.filters.has(eventName); } recordEvent(eventName, data) { const event = { name: eventName, data: JSON.parse(JSON.stringify(data || {})), timestamp: Date.now(), stack: new Error().stack }; this.events.push(event); // 实时显示 if (this.liveView) { this.displayEvent(event); } } displayEvent(event) { console.log(`[${new Date(event.timestamp).toLocaleTimeString()}] ${event.name}`, event.data); } getReport() { const report = { totalEvents: this.events.length, eventCounts: {}, timeline: [] }; this.events.forEach(event => { report.eventCounts[event.name] = (report.eventCounts[event.name] || 0) + 1; report.timeline.push({ time: event.timestamp, name: event.name }); }); return report; } exportToFile() { const report = this.getReport(); const data = JSON.stringify(report, null, 2); $share.sheet([data]); } } // 使用示例 const tracker = new EventTracker(eventBus); tracker.filters.add('user:updated'); tracker.filters.add('message:new'); tracker.liveView = true; tracker.start(); // 稍后生成报告 setTimeout(() => { const report = tracker.getReport(); console.log('Event Report:', report); }, 60000); ``` ### 2. 性能监控 监控 UI 更新性能: ```javascript class PerformanceMonitor { constructor(eventBus) { this.eventBus = eventBus; this.metrics = new Map(); } measureUIUpdate(componentId, updateFn) { const startTime = performance.now(); const startMemory = performance.memory?.usedJSHeapSize; const result = updateFn(); const endTime = performance.now(); const endMemory = performance.memory?.usedJSHeapSize; const metrics = { duration: endTime - startTime, memoryDelta: endMemory - startMemory, timestamp: Date.now() }; // 记录指标 if (!this.metrics.has(componentId)) { this.metrics.set(componentId, []); } this.metrics.get(componentId).push(metrics); // 发送警告 if (metrics.duration > 16) { // 超过一帧时间 this.eventBus.emit('performance:warning', { componentId, duration: metrics.duration, type: 'slowUpdate' }); } return result; } getAverageMetrics(componentId) { const componentMetrics = this.metrics.get(componentId) || []; if (componentMetrics.length === 0) return null; const sum = componentMetrics.reduce((acc, m) => ({ duration: acc.duration + m.duration, memoryDelta: acc.memoryDelta + m.memoryDelta }), { duration: 0, memoryDelta: 0 }); return { avgDuration: sum.duration / componentMetrics.length, avgMemoryDelta: sum.memoryDelta / componentMetrics.length, sampleCount: componentMetrics.length }; } } ``` ## 七、最佳实践总结 ### 1. 事件命名规范 使用清晰、一致的事件命名: ```javascript // 推荐的命名模式 const eventPatterns = { // 实体:动作 'user:login': '用户登录', 'user:logout': '用户登出', 'user:updated': '用户信息更新', // 组件:状态 'modal:opened': '模态框打开', 'modal:closed': '模态框关闭', // 数据:操作 'data:loaded': '数据加载完成', 'data:error': '数据加载错误', // 界面:交互 'ui:scroll': '界面滚动', 'ui:resize': '界面大小改变' }; ``` ### 2. 错误处理策略 建立统一的错误处理机制: ```javascript class ErrorBoundary { constructor(eventBus) { this.eventBus = eventBus; // 全局错误捕获 this.eventBus.on('*', this.handleError.bind(this)); } handleError(data) { if (data.error) { console.error('Event error:', data.error); // 显示用户友好的错误提示 this.showErrorNotification(data.error); // 上报错误 this.reportError(data.error); } } showErrorNotification(error) { $ui.toast(`发生错误: ${error.message}`, 2); } reportError(error) { // 发送错误报告到服务器 $http.post({ url: 'https://api.example.com/errors', body: { error: error.message, stack: error.stack, userAgent: $device.info, timestamp: Date.now() } }); } } ``` ### 3. 内存管理 确保正确清理事件监听器和资源: ```javascript class ComponentLifecycle { constructor(eventBus) { this.eventBus = eventBus; this.components = new WeakMap(); } register(component) { const lifecycle = { subscriptions: [], timers: [], resources: [] }; this.components.set(component, lifecycle); // 自动清理 component.destroy = () => { this.cleanup(component); }; } cleanup(component) { const lifecycle = this.components.get(component); if (lifecycle) { // 取消事件订阅 lifecycle.subscriptions.forEach(unsubscribe => unsubscribe()); // 清理定时器 lifecycle.timers.forEach(timer => clearTimeout(timer)); // 释放其他资源 lifecycle.resources.forEach(resource => resource.dispose()); this.components.delete(component); } } } ``` ## 结语 事件驱动架构为 JSBox 应用的 UI 更新提供了一个强大而灵活的解决方案。通过解耦数据变化和界面更新,我们可以构建更加可维护、可扩展的应用。 关键要点: 1. **使用事件总线**统一管理所有 UI 相关事件 2. **组件化思维**,每个 UI 组件独立订阅和处理事件 3. **性能优化**,使用批量更新、虚拟滚动等技术 4. **调试工具**,建立完善的事件追踪和性能监控 5. **最佳实践**,遵循命名规范,做好错误处理和内存管理 通过合理运用这些技术和模式,你可以构建出响应迅速、用户体验优秀的 JSBox 应用。记住,好的架构不是一蹴而就的,需要在实践中不断优化和完善。
配图 (可多选)
选择新图片文件或拖拽到此处
标签
更新文章
删除文章