自定义事件作为单向数据流的唯一出口,仅上报意图而不修改状态,需设bubbles/composed为true穿透Shadow DOM,由根节点统一拦截分发并审计,状态更新驱动视图重绘,禁止反向通信、嵌套派发与payload篡改。
可以用自定义事件模拟单向数据流,关键不是“用事件代替状态管理”,而是用事件作为唯一出口,把状态变更的发起权收束到可追踪、可拦截、可审计的通道里。
明确事件角色:只做“通知”,不做“修改”
自定义事件应严格承担“上报意图”的职责,而非直接操作数据。例如:
子组件点击按钮后,不调用 store.dispatch 或修改父组件 data,而是触发:
this.dispatchEvent(new CustomEvent('add-item', { detail: { text: 'new task' }, bubbles: true, composed: true }));- 事件名语义清晰(动词+名词),避免模糊如
'change'或'update' - 必须设置
bubbles: true和composed: true,确保能穿透 Shadow DOM 边界,被顶层监听器捕获
统一入口拦截:所有事件必须经由中央调度器
禁止组件直接监听自身或兄弟组件的自定义事件。所有业务事件应在应用根节点(如 <app-shell></app-shell>)集中注册并分发:
- 根组件监听
add-item、delete-item等事件,再交由状态管理模块(如 Aurelia Store、Pinia 或纯 JS reducer)处理 - 调度器中可统一添加日志、权限校验、防抖、事务包装等横切逻辑
- 事件未被调度器捕获时,应抛出警告(开发环境),避免“静默丢失”
状态更新与视图同步:事件不驱动重绘,状态驱动重绘
自定义事件本身不更新 DOM,也不触发响应式更新。它只触发状态变更;视图更新必须依赖状态的可观测性机制:
- 状态更新后,通过
store.state.subscribe()或computed等机制让组件自动响应 - 避免在事件回调中手动调用
this.render()或this.update()—— 这会绕过数据流,制造隐式依赖 - 若使用原生 Web Components,推荐搭配
signal(如 Lit 的signal)或Observable实现状态驱动的局部更新
补充约束:防止事件滥用的三道护栏
仅靠事件机制无法天然保证单向性,需配合设计约束:
-
禁止反向通信:不允许子组件通过事件向父组件请求数据(如
'get-config'),配置类数据应由父组件通过属性(attribute/property)单向注入 - 禁止事件嵌套派发:一个事件处理函数内不得再 dispatch 另一个业务事件,避免链式调用导致流向不可控
-
事件 payload 不可变:传入
detail的对象应为结构克隆或只读代理,防止下游意外篡改原始意图

















