如何在 Web 组件(原生 UI)之间进行通信?
Posted
技术标签:
【中文标题】如何在 Web 组件(原生 UI)之间进行通信?【英文标题】:How to communicate between Web Components (native UI)? 【发布时间】:2019-07-26 19:49:44 【问题描述】:我正在尝试将本机 Web 组件用于我的一个 UI 项目,而对于这个项目,我没有使用任何框架或库,如 Polymer 等。我想知道有没有最好的方法或其他方法就像我们在 angularjs/angular 中所做的那样在两个 Web 组件之间进行通信(例如消息总线概念)。
目前在 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 Web 组件集成的优秀库,如 postaljs 等。
【问题讨论】:
【参考方案1】:如果您将 Web 组件视为像 <div>
和 <audio>
这样的内置组件,那么您可以回答自己的问题。组件之间不相互通信。
一旦您开始允许组件直接相互通信,那么您实际上并没有组件,您拥有一个捆绑在一起的系统,并且您不能在没有组件 B 的情况下使用组件 A。这太紧密地捆绑在一起了。
相反,在拥有这两个组件的父代码中,您添加的代码允许您从组件 A 接收 事件 和 调用函数 或 设置参数 在组件 B 中,反之亦然。
话虽如此,内置组件的这条规则有两个例外:
<label>
标签:它使用for
属性来获取另一个组件的 ID,如果设置并有效,那么当您单击 @987654325 时,它会将焦点传递给另一个组件@
<form>
标记:这会查找子表单元素以收集发布表单所需的数据。
但是这两个仍然没有绑定到任何东西。 <label>
被告知focus
事件的接收者,并且仅在 ID 已设置且有效或作为子元素传递给第一个表单元素时才将其传递。而<form>
元素并不关心存在哪些子元素或有多少子元素,它只是通过其所有后代找到属于表单元素的元素并获取它们的value
属性。
但作为一般规则,您应该避免让一个兄弟组件直接与另一个兄弟组件对话。上面两个例子中的交叉通信方法可能是唯一的例外。
相反,您的父代码应该监听事件并调用函数或设置属性。
是的,您可以将该功能包装在一个新的父组件中,但请避免大量的麻烦,并避免使用意大利面条式代码。
作为一般规则,我绝不允许兄弟元素相互交谈,他们与父母交谈的唯一方式是通过事件。父母可以通过属性、属性和函数直接与孩子交谈。但在所有其他情况下都应避免。
【讨论】:
1. 和 2 都可以重写为仅使用事件,需要一些额外的工作,因为如果表单显示“ALLMYCHILDREN”,它不知道要处理多少响应;所以你需要某种时间来确定“最后的答复”。有点像学生进入我的教室,我不知道今天会有多少人或以什么顺序来。但是我有一个严格的规定。我在最后一个人进入后等待 2 分钟,然后锁上门(是的,用钥匙)...教他们基于事件的编程 :-) 表单没有时间问题,因为他们的孩子在提交表单时已经存在。我包括示例 1 和示例 2,以仅显示规则的两个例外。传统 DOM 元素中的所有其他内容都由事件处理并访问子属性和属性,或调用它们的函数。 非常感谢@Intervalia 对此的精彩解释。我理解 web-components 就像一个内置的 web 组件,它们的行为应该完全相同。正如您提到的,我还学习了父母、属性、属性等的概念,并尝试在我的项目中应用。 :) @Intervalia 假设我有一个包含两个子组件的视图组件:一个列表和一个工具栏。在列表(复选框)中选择一个项目会触发一个自定义事件,该事件会一直冒泡到父视图组件。如果选择了列表中的项目,工具栏应该启用用户可以在列表中使用的一些工具。选项是让视图直接与工具栏对话,或者传递事件以更新全局状态(想想 redux),然后视图在其中侦听状态的更改并更新工具栏。什么时候一个比另一个更可取? 如果您让组件 A 与组件 B 对话,那么您将两者联系在一起。如果您随后需要将 Component B 与 Component C 交换并且接口不同,那么您需要更改 Component A 以了解如何与 C 对话。如果您允许事件由父级处理,则父级需要知道如何与 C 对话。但这是由父组件而不是组件 A 编写的选择。因此,让父组件处理差异而不是让 A 与 B 或 C 一起工作更有意义。跨度> 【参考方案2】:工作示例
在您的父代码 (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>
【讨论】:
【参考方案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
元素不耦合到card
元素中的.move()
方法。
唯一的耦合是事件的名称:___FINDSLOT___
!!!
这意味着card
始终处于控制之中,相同的事件(名称) 可用于:
pile
是满堂彩?
...
在我的电子元素代码中,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));
,
【讨论】:
【参考方案4】: 如果您想处理松散耦合的自定义元素,自定义事件是最佳解决方案。
相反,如果一个自定义元素通过它的引用知道另一个,它可以调用它的自定义属性或方法:
//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 组件(原生 UI)之间进行通信?的主要内容,如果未能解决你的问题,请参考以下文章
如何为 react-native 本机 ui 组件创建打字稿类型定义?
Android -- 每日一问:两个 Fragment 之间如何进行通信 ?
如何解决反应原生 EventEmitterListener 警告