本机UI Web组件之间有哪些可能的通信方式?

时间:2019-03-05 10:57:39

标签: javascript html5 web-component custom-element native-web-component

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

当前在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 个答案:

答案 0 :(得分:2)

工作示例

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

// 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 = () => {
      // can: this.onsend() or not 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
// which subscribe chat-form send event, 
// receive message and set it to chat-history
// -----------------

myChatForm.addEventListener('send', e => {
  myChatHistory.add(e.detail.message)
});
body {background: white}
<chat-form id="myChatForm"></chat-form>
<chat-history id="myChatHistory"></chat-history>

答案 1 :(得分:1)

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

一旦开始允许组件彼此直接通信,那么您实际上就没有真正的组件将系统捆绑在一起,没有组件B就无法使用组件A。这太紧密了。

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

已经说过,内置组件对此规则有两个例外:

  1. <label>标记:它使用for属性获取另一个组件的ID,如果设置并有效,则在单击时将焦点传递到另一个组件在<label>

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

但是这两个都还没有被束缚。 <label>被告知focus事件的接收者,并且仅当ID设置且有效且作为子项传递给第一个表单元素时才传递。并且<form>元素并不关心存在哪些子元素,或者仅通过其所有后代来查找多少子元素,而这些元素是表单元素并获取其value属性。

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

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

是的,您可以将该功能包装在一个新的父级组件中,但是请您省下很多麻烦并避免使用意大利面条式的代码。

作为一般规则,我绝不允许兄弟姐妹元素互相交谈,而他们与父母交谈的唯一方法是通过事件。父母可以通过属性,属性和功能直接与子女交谈。但是在所有其他情况下都应避免使用它。

答案 2 :(得分:1)

+1表示其他两个答案,“事件”是最好的,因为然后组件之间的耦合松散

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

事件驱动的函数执行:

所以我用(伪代码):

定义纸牌/空当接龙游戏的元素:

-> 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始终处于控制之中,并且相同的事件(名称)可用于:

  • 卡可以去哪里?
  • 什么是最好的位置?
  • pile中的哪张卡可以满座?
  • ...

在我的电子元素代码pile中也没有耦合到evt.detail.id

CustomEvents仅发送函数



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

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

我现在有大量的电子元素,可用于创建任何纸牌游戏

..将在本月晚些时候在GitHub上

偷窥:

不需要任何库,编写自己的'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));
    },

    Danny.say(
      __NATIVE_ELEMENTS_LOVERS__, 
      () => mail('danny@engelman.nl' , 'Re: early access to your E-lements GitHub')
    );

答案 3 :(得分:0)

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

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

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

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

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

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

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

chatHistory.setAttributes( 'chat', 'active' )
chatHistory.dataset.username = `$(this.name)`