当前位置: 首页 > 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.dtcms.com/a/297956.html

相关文章:

  • Langchain学习——PromptTemplate
  • Class21卷积层的多输入通道和多输出通道
  • 基于纳米流体强化的切割液性能提升与晶圆 TTV 均匀性控制
  • 轻量级音乐元数据编辑器Metadata Remote
  • [NPUCTF2020]ReadlezPHP
  • iOS —— 天气预报仿写总结
  • SQL164 2021年11月每天新用户的次日留存率
  • ReAct Agent(LangGraph实现)
  • 去除视频字幕 2, 使用 PaddleOCR 选取图片中的字幕区域, 根据像素大小 + 形状轮廓
  • MCP 与传统集成方案深度对决:REST API、GraphQL、gRPC 全方位技术解析
  • react 内置hooks 详细使用场景,使用案例
  • 轮盘赌算法
  • Python爬虫实战:研究Talon相关技术构建电商爬虫系统
  • ZLMediaKit 源代码入门
  • Java排序算法之<选择排序>
  • IT领域需要“落霞归雁”思维框架的好处
  • 熵与交叉熵:从信息论到机器学习的「不确定性」密码
  • Jmeter的元件使用介绍:(四)前置处理器详解
  • 告别静态文档!Oracle交互式技术架构图让数据库学习“活“起来
  • 多步相移小记
  • epoll_event数据结构及使用案例详解
  • springboot(3.4.8)整合mybatis
  • 分布式方案 一 分布式锁的四大实现方式
  • android app适配Android 15可以在Android studio自带的模拟器上进行吗,还是说必须在真机上进行
  • HashMap底层实现原理与核心设计解析
  • AI同传领域,字节跳动与科大讯飞激战进行时
  • 【Linux系统】基础IO(下)
  • 深度学习篇---图像数据采集
  • classgraph:Java轻量级类和包扫描器
  • 深度学习篇---深度学习中的卡尔曼滤波