react:调度和监听自定义事件

问题描述 投票:0回答:3

我想将 Web 组件集成到我的 React 应用程序中。它们定义了一组自定义事件,封闭的 React 应用程序必须监听这些事件。

根据react文档

Web 组件发出的事件可能无法通过组件正确传播 反应渲染树。您需要手动将事件处理程序附加到 在 React 组件中处理这些事件。

我一直在努力完成这项工作。我可以从 React 组件分派自定义事件并在 dom 元素上监听它们。但我无法在其他 React 组件上捕获这些事件。

如何从 React 应用程序正确监听自定义事件(理想情况下,也能够分派它们)?

最小示例

我已经设置了一个最小的示例(在 sandbox.io 上实时编辑)。它由两个 div 组成,外部一个监听我的自定义事件,内部一个托管虚拟 dom。在虚拟 dom 中有两个组件。外部事件监听自定义事件,内部事件调度它。运行时,外部 div 注册自定义事件。在反应应用程序内部,它没有被捕获。

如果您想使用代码,我已将其设置为存储库:

git clone https://github.com/lhk/react_custom_events
cd react_custom_events
npm i
npm run start
# browser opens, look at the console output

index.html
,包含一个将监听自定义 dom 元素的 div,其内部是 React 应用程序的根。

<div id='listener'>
  <div id="react_root"></div>
</div>

React 应用程序由两个功能组件组成:Listener 和 Dispatcher。

index.tsx
渲染 React 应用程序并在 dom 上设置监听器
div
:

document.getElementById("listener")?.addEventListener("custom", ev => {
  console.log("dom received custom event");
});

ReactDOM.render(<Listener />, document.getElementById("react_root"));

这是两个组件,它们调度并监听自定义 dom 事件。

Listener.tsx

import React, { useEffect, useRef } from "react";
import Dispatcher from "./Dispatcher";

export default function Listener() {
  const divRef = useRef(null);
  useEffect(() => {
    (divRef.current as any).addEventListener("custom", (ev:any) => {
        console.log("react received custom event");
      });
  });
  return (
    <div ref={divRef}>
      <Dispatcher />
    </div>
  );
}

Dispatcher.tsx

import React, { useEffect, useRef } from "react";
import { customEvent } from "./events";

export default function Dispatcher() {
  const pRef = useRef(null);
  useEffect(() => {
    (pRef.current as any).dispatchEvent(customEvent);
  });
  return (
    <div>
      <p ref={pRef}>Some Text</p>
    </div>
  );
}

最后,自定义事件声明如下:

export var customEvent = new Event('custom', { bubbles: true });

相关问题:

这个问题听起来很相似:Listening for web component events in React

但这并不是真正关于React中的自定义事件系统。相反,它询问如何在聚合物组件上公开事件属性。

这个问题也是关于自定义事件的,但不是在react的上下文中: 如何监听Web组件定义的自定义事件

这个问题似乎只是一个语法错误:React 应用程序中的 addEventListener 不起作用

javascript reactjs dom-events web-component
3个回答
9
投票

在我看来,这在反应中根本不可能。

React 提供了一个合成事件系统。这是出于兼容性原因而实现的,合成事件在浏览器之间公开相同的 API。在内部,React 将原生 DOM 事件包装成合成事件。

但是,这些合成事件也带来了一系列限制:

  • 合成事件不涵盖所有本地事件。最值得注意的是 onInput 和 onChange 方面的变化。
  • 没有自定义事件的合成版本
  • 本机(自定义)事件将在虚拟 dom 中冒泡,而不触发任何事件侦听器。离开虚拟dom后,他们将继续在dom的其余部分冒泡。这就是我的示例代码中发生的情况。
  • 无法自己调度合成事件

可以在虚拟 dom 中监听自定义事件,但只能在调度它们的同一元素上。以下代码将捕获该事件:

export default function Dispatcher() {
  const pRef = useRef(null);
  useEffect(() => {
    (pRef.current as any).addEventListener('custom', ()=>{
      console.log('react caught custom event directly on component');
    });
    (pRef.current as any).dispatchEvent(customEvent);
  });
  return (
    <div>
      <p ref={pRef}>Some Text</p>
    </div>
  );
}

我认为这使得自定义事件几乎毫无用处。如果你必须获取触发自定义事件的特定 dom 节点的引用,你不妨给它一个回调。

如果您有兴趣阅读此内容,可以查看各种 github 问题。不幸的是,Facebook React 团队似乎对它们进行了相当严格的调节。他们只是关闭而没有进一步评论:(


1
投票

这可以通过以下方式轻松实现:

自定义组件.js:

this.dispatchEvent(new CustomEvent('some-event-name', {
  bubbles: true,
  detail: eventDataToSendAsEvent
}));

组件.jsx:

const ref = useRef();
const handleEvent = (data) => {
  console.log('Event Handled', data);
};

useEventListener("some-event-name", handleEvent, ref.current);

useEventListener.js

import { useEffect } from 'react';

const useEventListener = (eventType, listener, targetElement = window) => {
  useEffect(() => {
    if (targetElement?.addEventListener) {
      targetElement.addEventListener(eventType, listener);
    }

    return () => {
      // https://github.com/niksy/throttle-debounce#cancelling
      if (listener?.cancel) {
        listener.cancel();
      }

      // Remove the event listeners
      if (targetElement?.removeEventListener) {
        targetElement.removeEventListener(eventType, listener);
      }
    };
  }, [eventType, listener, targetElement]);
};

export default useEventListener;

您可以在这个 openapi explorer 沙箱中看到一个工作示例。


0
投票

对于 2024 年到达这里的任何人,使用由 window 对象调度的

CustomEvent
对我来说很有效:

示例:

子组件:

...
<Minus
  onClick={() => {
    const ev = new CustomEvent('selected-profile', {detail: profile.id});
    window.dispatchEvent(ev);
  }
...

父组件:

...
const addProfile = useCallback((e: any) => {
  console.log('selected profile ' + e.detail);
}, []);

useEffect(() => {
  window.addEventListener('selected-profile', addProfile);
  return () => {
    window.removeEventListener('selected-profile', addProfile);
  };
});
...
© www.soinside.com 2019 - 2024. All rights reserved.