Web Components(原生UI)之间如何通信?

How to communicate between Web Components (native UI)?

我正在尝试为我的一个 UI 项目使用本机 Web 组件,而对于这个项目,我没有使用任何框架或库,如 Polymer 等。我想知道有没有在两个 Web 组件之间进行通信的最佳方式或其他方式,就像我们在 angularjs/angular 中所做的那样(如消息总线概念)。

目前在 UI 网络组件中,我使用 dispatchevent 发布数据和接收数据,我使用 addeventlistener。 例如,有 2 个 Web 组件,ChatForm 和 ChatHistory。

// chatform webcomponent on submit text, publish chattext data 
this.dispatchEvent(new CustomEvent('chatText', {detail: chattext}));

// chathistory webcomponent, receive chattext data and append it to chat list
this.chatFormEle.addEventListener('chatText', (v) => {console.log(v.detail);});

请告诉我还有哪些其他方法可用于此目的。任何可以轻松与原生 UI 网络组件集成的好库,如 postaljs 等。

如果您想处理松散耦合的自定义元素,

自定义事件 是最佳解决方案。

相反,如果一个自定义元素通过引用知道另一个元素,它可以调用其自定义 属性 或方法:

//in chatForm element
chatHistory.attachedForm = this
chatHistory.addMessage( message )
chatHistory.api.addMessage( message )

在上面的最后一个示例中,通信是通过 api 属性.

公开的专用对象完成的

您还可以混合使用事件(以一种方式)和方法(以另一种方式),具体取决于自定义元素的链接方式。

最后,在一些基本消息的情况下,您可以通过 HTML 属性:

传递(字符串)数据
chatHistory.setAttributes( 'chat', 'active' )
chatHistory.dataset.username = `$(this.name)`

如果您将 Web 组件视为 <div><audio> 等内置组件,那么您可以回答自己的问题。这些组件不会相互通信。

一旦您开始允许组件直接相互对话,那么您实际上并没有组件,您拥有一个捆绑在一起的系统,如果没有组件 B,您将无法使用组件 A。这捆绑得太紧了。

相反,在拥有这两个组件的 parent 代码中,您添加允许您从组件 A 接收 事件 调用的代码函数在组件B中设置参数,反之亦然。

话虽如此,对于内置组件,此规则有两个例外:

  1. <label> 标签:它使用 for 属性来获取另一个组件的 ID,如果已设置且有效,则将焦点传递给另一个组件单击 <label>

  2. 时的组件
  3. <form> 标签:这会查找 children 的表单元素,以收集 post 表单所需的数据。

但这两者仍然没有任何关联。 <label> 被告知 focus 事件的接收者,并且仅当 ID 已设置且有效时才将其传递给第一个表单元素作为 child。 <form> 元素不关心存在哪些 child 元素或有多少元素,它只是遍历其所有后代查找属于表单元素的元素并获取它们的 value 属性。

但作为一般规则,您应该避免让一个同级组件直接与另一个同级组件对话。上面两个例子中的交叉通信方法可能是唯一的例外。

您的 parent 代码应该监听事件并调用函数或设置属性。

是的,您可以将该功能包装在一个新的 parent 组件中,但请避免麻烦并避免面条式代码。

作为一般规则,我绝不允许兄弟元素相互交谈,他们与 parent 的唯一联系方式是通过 事件 。 Parents 可以通过属性、属性和函数直接与他们的 children 对话。但在所有其他情况下都应避免使用。

+1 对于其他两个答案,事件是最好的,因为那样组件是松散的 耦合


另见:https://pm.dartus.fr/blog/a-complete-guide-on-shadow-dom-and-event-propagation/


请注意,在自定义事件的 detail 中,您可以发送任何内容。

事件驱动函数执行:

所以我使用(伪代码):

定义 Solitaire/Freecell 游戏的元素:

-> game Element
  -> pile Element
    -> slot Element
      -> card element
  -> pile Element
    -> slot Element
      -> empty

当一张牌(由用户拖动)需要移动到另一堆时,

它发送一个事件(将 DOM 冒泡到游戏元素)

    //triggered by .dragend Event
    card.say(___FINDSLOT___, {
                                id, 
                                reply: slot => card.move(slot)
                            });    

注:reply是函数定义

因为 all 堆被告知要在游戏元素处监听 ___FINDSLOT___ 事件 ...

   pile.on(game, ___FINDSLOT___, evt => {
                                      let foundslot = pile.free(evt.detail.id);
                                      if (foundslot.length) evt.detail.reply(foundslot[0]);
                                    });

只有匹配evt.detail.id的一堆回复:

!!!通过 执行 函数 cardevt.detail.reply

中发送

并获得技术支持:函数在 pile 范围内执行!

(以上代码为伪代码!)

为什么?!

可能看起来很复杂;
重要的部分是 pile 元素 未耦合 card 元素中的 .move() 方法。

只有耦合是事件的名称:___FINDSLOT___!!!

这意味着 card 始终处于控制之中,相同的事件(名称) 可用于:

  • 卡片可以去哪里?
  • 最佳位置是什么?
  • 河牌pile中的哪张牌是葫芦?
  • ...

在我的 E-lements 代码中 pile 也没有耦合到 evt.detail.id

CustomEvents 仅发送函数



.say().on() 是我为 dispatchEventaddEventListener

自定义的方法(在每个元素上)

我现在有一堆可以用来制作任何纸牌游戏的电子元素

不需要任何库,自己写'Message Bus'

我的 element.on() 方法只有几行代码围绕 addEventListener 函数,因此可以轻松删除它们:

    $Element_addEventListener(
        name,
        func,
        options = {}
    ) {
        let BigBrotherFunc = evt => {                     // wrap every Listener function
            if (evt.detail && evt.detail.reply) {
                el.warn(`can catch ALL replies '${evt.type}' here`, evt);
            }
            func(evt);
        }
        el.addEventListener(name, BigBrotherFunc, options);
        return [name, () => el.removeEventListener(name, BigBrotherFunc)];
    },
    on(
        //!! no parameter defintions, because function uses ...arguments
    ) {
        let args = [...arguments];                                  // get arguments array
        let target = el;                                            // default target is current element
        if (args[0] instanceof HTMLElement) target = args.shift();  // if first element is another element, take it out the args array
        args[0] = ___eventName(args[0]) || args[0];                 // proces eventNR
        $Element_ListenersArray.push(target.$Element_addEventListener(...args));
    },

.say( ) 是单线:

    say(
        eventNR,
        detail,             //todo some default something here ??
        options = {
            detail,
            bubbles: 1,    // event bubbles UP the DOM
            composed: 1,   // !!! required so Event bubbles through the shadowDOM boundaries
        }
    ) {
        el.dispatchEvent(new CustomEvent(___eventName(eventNR) || eventNR, options));
    },

工作示例

在您的父代码 (html/css) 中,您应该订阅 <chat-form> 发出的事件并通过执行其方法将事件数据发送到 <chat-history>(下面的 add示例)

// WEB COMPONENT 1: chat-form
customElements.define('chat-form', class extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `Form<br><input id="msg" value="abc"/>
      <button id="btn">send</button>`;

    btn.onclick = () => {
      // alternative to below code
      // use this.onsend() or non recommended eval(this.getAttribute('onsend'))
      this.dispatchEvent(new CustomEvent('send',{detail: {message: msg.value} }))
      msg.value = '';
    }
  }
})


// WEB COMPONENT 2: chat-history
customElements.define('chat-history', class extends HTMLElement {
  add(msg) {
    let s = ""
    this.messages = [...(this.messages || []), msg];
    for (let m of this.messages) s += `<li>${m}</li>`
    this.innerHTML = `<div><br>History<ul>${s}</ul></div>`
  }
})


// -----------------
// PARENT CODE 
// (e.g. in index.html which use above two WebComponents)
// Parent must just subscribe chat-form send event, and when
// receive message then it shoud give it to chat-history add method
// -----------------

myChatForm.addEventListener('send', e => {
  myChatHistory.add(e.detail.message)
});
body {background: white}
<h3>Hello!</h3>

<chat-form id="myChatForm"></chat-form>

<div>Type something</div>

<chat-history id="myChatHistory"></chat-history>

我遇到了同样的问题,由于找不到合适的库,我决定自己写一个。

现在开始:https://www.npmjs.com/package/seawasp

SeaWasp 是一个 WebRTC 数据层,它允许组件(或框架等)之间进行通信。

您只需导入它,注册一个连接(又名触手 ;)),然后您就可以发送和接收消息了。

我正在积极努力,所以如果您有任何反馈/需要的功能,请告诉我:)。