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中设置参数,反之亦然。
话虽如此,对于内置组件,此规则有两个例外:
<label>
标签:它使用 for
属性来获取另一个组件的 ID,如果已设置且有效,则将焦点传递给另一个组件单击 <label>
时的组件
<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
的一堆回复:
!!!通过 执行 函数 card
在 evt.detail.reply
中发送
并获得技术支持:函数在 pile
范围内执行!
(以上代码为伪代码!)
为什么?!
可能看起来很复杂;
重要的部分是 pile
元素 未耦合 到 card
元素中的 .move()
方法。
只有耦合是事件的名称:___FINDSLOT___
!!!
这意味着 card
始终处于控制之中,相同的事件(名称) 可用于:
- 卡片可以去哪里?
- 最佳位置是什么?
- 河牌
pile
中的哪张牌是葫芦?
- ...
在我的 E-lements 代码中 pile
也没有耦合到 evt.detail.id
,
CustomEvents 仅发送函数
.say()
和 .on()
是我为 dispatchEvent
和 addEventListener
自定义的方法(在每个元素上)
我现在有一堆可以用来制作任何纸牌游戏的电子元素
不需要任何库,自己写'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 数据层,它允许组件(或框架等)之间进行通信。
您只需导入它,注册一个连接(又名触手 ;)),然后您就可以发送和接收消息了。
我正在积极努力,所以如果您有任何反馈/需要的功能,请告诉我:)。
我正在尝试为我的一个 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中设置参数,反之亦然。
话虽如此,对于内置组件,此规则有两个例外:
<label>
标签:它使用for
属性来获取另一个组件的 ID,如果已设置且有效,则将焦点传递给另一个组件单击<label>
时的组件
<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
的一堆回复:
!!!通过 执行 函数 card
在 evt.detail.reply
并获得技术支持:函数在 pile
范围内执行!
(以上代码为伪代码!)
为什么?!
可能看起来很复杂;
重要的部分是 pile
元素 未耦合 到 card
元素中的 .move()
方法。
只有耦合是事件的名称:___FINDSLOT___
!!!
这意味着 card
始终处于控制之中,相同的事件(名称) 可用于:
- 卡片可以去哪里?
- 最佳位置是什么?
- 河牌
pile
中的哪张牌是葫芦? - ...
在我的 E-lements 代码中 pile
也没有耦合到 evt.detail.id
,
CustomEvents 仅发送函数
.say()
和 .on()
是我为 dispatchEvent
和 addEventListener
我现在有一堆可以用来制作任何纸牌游戏的电子元素
不需要任何库,自己写'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 数据层,它允许组件(或框架等)之间进行通信。
您只需导入它,注册一个连接(又名触手 ;)),然后您就可以发送和接收消息了。
我正在积极努力,所以如果您有任何反馈/需要的功能,请告诉我:)。