19

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

目前在 UI Web 组件中,我使用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 Web 组件集成的好库,如 postaljs 等。

4

4 回答 4

21

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

一旦您开始允许组件直接相互通信,那么您实际上并没有组件,您拥有一个捆绑在一起的系统,并且您不能在没有组件 B 的情况下使用组件 A。这太紧密地捆绑在一起了。

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

话虽如此,内置组件的这条规则有两个例外:

  1. <label>标签:它使用for属性来获取另一个组件的 ID,如果设置且有效,那么当您单击<label>

  2. 标签:这会寻找作为子元素的<form>表单元素,以收集发布表单所需的数据。

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

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

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

是的,您可以将该功能包装在一个新的父组件中,但请避免大量的悲伤并避免意大利面条式代码。

作为一般规则,我从不允许兄弟元素相互交谈,他们与父母交谈的唯一方法是通过事件。父母可以通过属性、属性和函数直接与孩子交谈。但在所有其他情况下都应避免。

于 2019-03-06T16:07:09.287 回答
8

工作示例

在您的父代码(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>

于 2020-02-25T17:18:52.660 回答
3

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


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


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

事件驱动的函数执行:

所以我使用(伪代码):

定义单人纸牌/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是一个函数定义

因为所有___FINDSLOT___在游戏元素中被告知要监听事件的堆......

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

只有一堆匹配的evt.detail.id响应:

!!!通过执行card发送的函数evt.detail.reply

并获得技术:该功能在pile范围内执行!

以上代码为伪代码!

为什么?!

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

唯一的耦合是事件的名称:___FINDSLOT___ !!!

这意味着card始终处于控制之中,并且相同的 Event(Name)可用于:

  • 卡可以去哪里?
  • 最佳位置是什么?
  • 牌中哪张牌pile会成为葫芦?
  • ...

在我的电子元素代码中,两者pile都没有耦合evt.detail.id

CustomEvents 只发送函数



.say()并且.on()是我的自定义方法(在每个元素上dispatchEventaddEventListener

我现在有一些可用于创建任何纸牌游戏的元素

不需要任何库,编写自己的“消息总线”

我的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));
    },
于 2019-03-08T08:16:44.990 回答
2

如果您想处理松散耦合的自定义元素,自定义事件是最佳解决方案。

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

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

在上面的最后一个示例中,通信是通过通过api属性公开的专用对象完成的。

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

最后,在某些消息是基本的情况下,您可以通过HTML 属性传达(字符串)数据:

chatHistory.setAttributes( 'chat', 'active' )
chatHistory.dataset.username = `$(this.name)`
于 2019-03-05T11:17:05.460 回答