簡體   English   中英

如何在 Web 組件(原生 UI)之間進行通信?

[英]How to communicate between Web Components (native UI)?

我正在嘗試為我的一個 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);});

請讓我知道為此目的還有哪些其他方法。 任何好的庫,如 postaljs 等,可以輕松地與本機 UI Web 組件集成。

如果您將 Web 組件視為像<div><audio>這樣的內置組件,那么您可以回答您自己的問題。 這些組件不會相互通信。

一旦您開始允許組件直接相互通信,那么您就沒有真正擁有組件,您擁有一個綁定在一起的系統,並且您不能在沒有組件 B 的情況下使用組件 A。這太緊密地綁定在一起了。

相反,在擁有這兩個組件的父代碼中,添加允許您從組件 A 接收事件並在組件 B 中調用函數設置參數的代碼,反之亦然。

話雖如此,對於內置組件,此規則有兩個例外:

  1. <label>標簽:它使用for屬性接收另一個組件的 ID,如果設置且有效,那么當您單擊<label>時,它將焦點傳遞給另一個組件

  2. <form>標簽:它會查找作為子元素的表單元素,以收集發布表單所需的數據。

但是這兩者仍然沒有與任何事情相關聯。 <label>被告知focus事件的接收者,並且僅當 ID 設置且有效或作為子元素傳遞給第一個表單元素時才將其傳遞。 並且<form>元素並不關心存在哪些子元素或有多少它只是通過其所有后代查找作為表單元素的元素並獲取它們的value屬性。

但作為一般規則,您應該避免讓一個兄弟組件直接與另一個兄弟組件對話。 上面兩個例子中的交叉通信方法可能是唯一的例外。

相反,您的父代碼應該偵聽事件並調用函數或設置屬性。

是的,您可以將該功能包裝在一個新的父組件中,但請避免大量的痛苦並避免意大利面條式代碼。

作為一般規則,我從不允許兄弟姐妹元素相互交談,他們與父母交談的唯一方式是通過事件 父母可以通過屬性、特性和功能直接與他們的孩子交談。 但在所有其他條件下都應該避免。

工作示例

在您的父代碼 (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 = () => { // 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}
 <h3>Hello!</h3> <chat-form id="myChatForm"></chat-form> <div>Type something</div> <chat-history id="myChatHistory"></chat-history>

+1 對於其他兩個答案,事件是最好的,因為組件松散耦合


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


請注意,在自定義事件的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匹配的evt.detail.id響應:

!!! 通過執行evt.detail.reply發送的功能card

並獲得技術:該函數在pile范圍內執行!

以上代碼為偽代碼!

為什么?!

可能看起來很復雜;
重要的部分是pile元素沒有耦合card元素中的.move()方法。

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

這意味着card始終處於控制之中,並且相同的 Event(Name)可用於:

  • 哪里可以辦卡?
  • 最好的位置是什么?
  • 河牌pile哪張牌構成滿堂?
  • ...

在我的 E-lements 代碼pile也沒有與evt.detail.id耦合,

CustomEvents 只發送函數



.say().on()是我用於dispatchEventaddEventListener自定義方法(在每個元素上)

我現在有一些可用於創建任何紙牌游戲的 E-lements

不需要任何庫,編寫自己的“消息總線”

我的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));
    },

如果您想處理松散耦合的自定義元素,自定義事件是最好的解決方案。

相反,如果一個自定義元素通過引用了解另一個元素,則它可以調用其自定義屬性或方法

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

在上面的最后一個示例中,通信是通過api屬性公開的專用對象完成的。

您還可以根據自定義元素的鏈接方式混合使用事件(以一種方式)和方法(以另一種方式)。

最后,在一些基本消息的情況下,您可以通過HTML 屬性來交流(字符串)數據:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM