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

从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.lryc.cn/news/600110.html

相关文章:

  • C++ 多线程同步机制详解:互斥锁、条件变量与原子操作
  • Python Multiprocessing 进程池完全教程:从理论到实战
  • 数据结构(3)单链表
  • [尚庭公寓]14-找房模块
  • Canal 1.1.7的安装
  • 习题5.6 “数学黑洞“
  • PHP插件开发中的一个错误:JSON直接输出导致网站首页异常
  • 纸板留声机:用ESP32和NFC打造会唱歌的复古装置
  • 手语式映射:Kinova Gen3 力控机械臂自适应控制的研究与应用
  • 秒收蜘蛛池解析机制的原理
  • PPIO上线阿里旗舰推理模型Qwen3-235B-A22B-Thinking-2507
  • ATR2652SGNSS全频段低噪声放大器
  • PostgreSQL对象权限管理
  • GPU 驱动安装升级测试
  • [NPUCTF2020]ReadlezPHP
  • CSS 盒子模型学习版的理解
  • C语言第 9 天学习笔记:数组(二维数组与字符数组)
  • ODFM(正交频分复用)系统中加入汉明码(Hamming Code)的主要目的是增强抗误码能力,通过**前向纠错(FEC)**机制提高传输可靠性
  • KNN算法:从原理到实战全解析
  • Kubernetes深度解析:企业级容器编排平台的核心实践
  • 内存 管理
  • LeetCode 233:数字 1 的个数
  • ACL:访问控制列表
  • 大数据中心——解读60页IDC云数据中心机房运维服务解决方案【附全文阅读】
  • MMRotate ReDet ReFPN 报错 `assert input.type == self.in_type`
  • Linux的磁盘存储管理实操——(下二)——逻辑卷管理LVM的扩容、缩容
  • ComfyUI中运行Wan 2.1工作流,电影级视频,兼容Mac, Windows
  • 一些常见的网络攻击方式
  • 与 TRON (波场) 区块链进行交互的命令行工具 (CLI): tstroncli
  • 关闭chrome自带的跨域限制,简化本地开发