当前位置: 首页 > web >正文

从Taro的Dialog.open出发,学习远程控制组件之【事件驱动】

源码

export const BaseDialog: FunctionComponent<Partial<DialogProps>> & {open: typeof openclose: typeof close
} = (props) => {...const {params: {...},setParams,} = useParams(mergeProps(defaultProps, props))useCustomEvent(id as string,({ status, options }: { status: boolean; options: any }) => {if (status) {setParams({ ...options, visible: true })} else {setParams({ ...options, visible: false })}})...
}export function open(selector: string, options: Partial<typeof defaultProps>) {// eslint-disable-next-line react-hooks/rules-of-hooksconst path = useCustomEventsPath(selector)customEvents.trigger(path, { status: true, options })
}export function close(selector: string) {// eslint-disable-next-line react-hooks/rules-of-hooksconst path = useCustomEventsPath(selector)customEvents.trigger(path, { status: false })
}BaseDialog.displayName = 'NutDialog'
BaseDialog.open = open
BaseDialog.close = close
export const customEvents = new Events()export function useCustomEventsPath(selector?: string) {selector = selector || ''const path = getCurrentInstance().router?.pathreturn path ? `${path}__${selector}` : selector
}export function useCustomEvent(selector: string, cb: any) {const path = useCustomEventsPath(selector)useEffect(() => {customEvents.on(path, cb)return () => {customEvents.off(path)}}, [])const trigger = <T = any>(args: T) => {customEvents.trigger(path, args)}const off = () => {customEvents.off(path)}return [trigger, off]
}export function useParams<T = any>(args: T) {const forceUpdate = useForceUpdate()const stateRef = useRef(args)const currentRef = useRef<T>()const previousRef = useRef<T>()if (!isEqual(currentRef.current, args)) {previousRef.current = currentRef.currentcurrentRef.current = argsstateRef.current = args}const setParams = (args: T) => {stateRef.current = { ...stateRef.current, ...args }forceUpdate()}const params = stateRef.currentreturn { params, setParams }
}

基于事件的组件通信机制

在组件之间通过自定义事件进行通信(发布/订阅模式)

open、close

通过事件路径机制远程控制组件行为的方法

  • 根据 selector 构造一个唯一事件路径;
  • 然后通过 customEvents.trigger() 触发一个事件;
  • 组件内部监听这个路径的事件,收到 { status: true } 后,就会“打开/关闭”对话框。

customEvents

全局的事件总线(发布-订阅系统),用于组件间通过事件通信(发布/订阅模式)

useCustomEventsPath

生成事件通信路径,为组件内的事件触发/监听系统提供唯一标识,确保同一页面多个组件之间的事件不会冲突

useCustomEvent

监听对话框“事件总线”

  • 注册监听器:在组件 mount 时监听路径对应的事件,触发了事件就会执行 cb,从而更新组件内部状态。
  • 销毁监听器:在组件 unmount 时自动解绑监听 ,防止内存泄漏。
  • 返回操作工具
    • trigger(args):触发对应路径事件。
    • off():手动取消监听(可选)。

useParams

这个 Hook 是一个轻量的、非状态驱动的参数存储器。

主要功能:

  • params:当前参数。
  • setParams(newPartial):设置新参数并强制组件刷新。
  • 内部使用 useRef 存储状态,避免频繁 re-render。
  • 使用 lodash.isequal浅变更检测,避免不必要的更新。

对比 useState

  • useParams 更适合用于组件“打开时传入参数”的存储;
  • 比如弹窗打开后接收参数,并在内部读取和变更,但不需要 prop 方式控制。

静态方法挂载

并不是在「传入」 openclose,而是在声明 Dialog 这个组件的类型扩展:除了是一个函数组件(FunctionComponent),它还具有两个静态方法 openclose

你看到的含义
& { open: typeof open }把类型合并到组件对象上,使 TypeScript 知道它有这些静态方法
Dialog.open = open这是实际的实现,把方法挂到组件上

远程控制组件(解耦)

彻底解耦组件的显示控制权,让组件行为可被远程、跨层级、非嵌套方式调用,实现 React 中的“服务式组件使用方式”。

远程控制组件:指在不直接传入 props、不嵌套在父组件中的情况下,在任何地方(例如业务逻辑层、工具函数、服务层)就能直接“控制”组件的行为,比如打开弹窗。

传统方式的问题:props 方式耦合性强

function App() {const [open, setOpen] = useState(false)return (<><button onClick={() => setOpen(true)}>打开弹窗</button><Dialog open={open} onClose={() => setOpen(false)} /></>)
}
问题说明
耦合性高你必须在父组件声明 open
状态,并通过 props 传入子组件
无法跨组件调用如果要在另一个组件(比如 Header)里控制 Dialog,必须中间层层传递回调
状态提升复杂多个弹窗、多层组件时,状态提升会导致“状态穿透地狱”
父组件控制权太多父组件得管理打开/关闭逻辑、参数逻辑、动画状态等

事件式控制:彻底解耦

解耦 = 不需要显式父子通信关系,也不需要依赖上下文(如 props/context)。 你只要调用一个静态方法,就能完成一切 —— 这就是彻底解耦。

  • 不需要父组件声明 useState
  • 不需要 <Dialog open={xxx} />
  • 不用写 onClose 回调
  • 只需要你在页面里挂一个组件 <Dialog selector="my-dialog" /> 就可以
  • 甚至你在 service 层也能触发它
特性传统 props 控制事件式控制(open()
组件位置必须嵌套可以任意放置
控制方式由父组件状态控制全局或业务代码中调用即可
多组件支持控制多个难每个组件监听独立事件路径
状态管理由父组件集中管理每个组件内部自控
适合场景小项目、页面内组件全局组件库、低代码平台、复杂 UI 系统

事件、路径、标识路径

事件(Event) 是程序中的一种“信号”或“通知”,代表某个动作发生了。

  • 用户点击按钮 → 触发一个 “点击事件”
  • 表单提交 → 触发一个 “提交事件”
  • 你调用了某个函数 open() → 触发一个 “自定义事件”

路径(Path) 通常指的是某个东西的位置或地址。

const path = getCurrentInstance().router?.path

标识路径是用来唯一标识一个事件的字符串,通常由:

  • 当前页面路径
  • 加上一个特定的事件名(selector)

组合而成。

事件驱动(Event-Driven)

事件驱动是一种编程模式,它的核心思想是:

“当某个事件发生时,系统会触发并执行与该事件相关的逻辑。”

在 UI 和前端开发中,“事件驱动”意味着:

  • 你不主动调用某个函数去更新状态,
  • 而是注册监听器,等待事件发生后触发动作。
// 1. 注册监听器
customEvents.on('dialog__open', (params) => {showDialog(params)
})// 2. 触发事件
customEvents.trigger('dialog__open', { title: '确认删除?' })
  • on():注册监听函数
  • trigger():触发事件(并通知所有监听者)
  • off():取消监听
[业务代码调用 Dialog.open()][trigger 一个事件: "dialog__open"][Dialog 组件内部 useEffect 注册监听 "dialog__open"][事件到来 → 执行 setVisible(true), setParams()][Dialog 显示了,并渲染对应内容]

发布/订阅模式

发布/订阅模式的核心思想是解耦。在这种模式下,有两个主要角色:

  1. 发布者(Publisher):负责发布消息或事件,但不知道谁会接收这个消息。
  2. 订阅者(Subscriber):负责订阅某个事件,并在事件发布时收到通知。

发布者和订阅者之间没有直接的联系,它们通过一个中介(通常是一个事件总线消息队列)来进行通信。这种方式使得发布者和订阅者可以互相独立地工作,减少耦合。

  1. 发布事件

open 函数中,使用了 customEvents.trigger 来发布一个事件。

customEvents.trigger(path, { status: true, options })

这里的 path 是事件的标识,{ status: true, options } 是要发布的消息内容。

  1. 订阅事件

useCustomEvent 中,组件会订阅某个事件,通过 customEvents.on(path, cb) 来注册事件回调函数 cb

customEvents.on(path, cb)

当事件 path 被触发时,cb 会被执行。

  1. 取消订阅

useCustomEvent 中,组件也可以在不需要监听时通过 customEvents.off(path) 来移除订阅。

customEvents.off(path)

发布/订阅模式的好处在于它可以让不同模块之间解耦。发布者并不知道谁在订阅它发布的事件,而订阅者也无需知道谁在发布事件。它们通过事件总线来间接交流,降低了系统的复杂度和模块之间的依赖。

http://www.xdnf.cn/news/16357.html

相关文章:

  • OAuth 2.0 安全最佳实践 (RFC 9700) password 授权类型已经不推荐使用了,将在计划中移除
  • JS与Go:编程语言双星的碰撞与共生
  • vue2+node+express+MongoDB项目安装启动启动
  • go语言基础教程:【2】基础语法:基本数据类型(整形和浮点型)
  • js实现宫格布局图片放大交互动画
  • android app适配Android 15可以在Android studio自带的模拟器上进行吗,还是说必须在真机上进行
  • 无人机视觉模块技术解析
  • 【LeetCode Solutions】LeetCode 热题 100 题解(1 ~ 5)
  • [CSS]让overflow不用按shift可以滚轮水平滚动(纯CSS)
  • 【数据库】AI驱动未来:电科金仓新一代数据库一体机如何重构性能边界?
  • 半相合 - 脐血联合移植
  • Kingbasepostgis 安装实践
  • Go 官方 Elasticsearch 客户端 v9 快速上手与进阶实践*
  • R 语言绘制六种精美热图:转录组数据可视化实践(基于 pheatmap 包)
  • Redis替代方案:腾讯云TDSQL-C内存优化实战,TPS秒上涨
  • 大语言模型生成式人工智能企业应用
  • 水库大坝安全监测的主要内容
  • 微算法科技(NASDAQ:MLGO)采用分布式哈希表优化区块链索引结构,提高区块链检索效率
  • mac下 vscode 运行 c++无法弹出窗口
  • 《C++初阶之STL》【vector容器:详解 + 实现】
  • 智能问答分类系统:基于SVM的用户意图识别
  • Android Paging 分页加载库详解与实践
  • 航段导航计算机 (Segment_Navigator) 设计与实现
  • 重构 MVC:让经典架构完美适配复杂智能系统的后端业务逻辑层(内附框架示例代码)
  • 【MacOS】发展历程
  • HTTP 请求方法有哪些?
  • 《基于电阻抗断层扫描(EIT)驱动的肌肉骨骼模型表征人体手臂动态意图用于人机交互》论文解读
  • 当人机交互迈向新纪元:脑机接口与AR/VR/MR的狂飙之路
  • Spring Cloud Gateway 服务网关
  • 2025年第四届创新杯(原钉钉杯)赛题浅析-助攻快速选题