我正在尝试为我的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等。
答案 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和调用函数或设置参数接收事件的代码,反之亦然。
已经说过,内置组件对此规则有两个例外:
<label>
标记:它使用for
属性获取另一个组件的ID,如果设置并有效,则在单击时将焦点传递到另一个组件在<label>
<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()
是我对dispatchEvent
和addEventListener
我现在有大量的电子元素,可用于创建任何纸牌游戏
..将在本月晚些时候在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)`