为什么我不使用网络组件

将来,我主要为自己编写这篇文章,以便在有人问我为什么对Web组件表示怀疑以及为什么默认情况下Svelte不编译为Web组件时可以参考。 (但是,它可以编译成Web组件,也可以与Web组件集成,这在Custom Elements Everywhere上获得了极高的评价。)


以下任何内容均不应解释为对Web组件上的辛苦工作的批评。 也许我在本出版物中也犯了错误,在这种情况下,我很乐意修改。 我也没有声明您不应使用Web组件。 他们有自己的范围。 我只是解释为什么它们不适合我。


1.逐步改进


这可能是一种过时的观念,但是我认为网站应该尽可能不使用JavaScript。 没有JS的Web组件不起作用。 对于固有的交互性内容(例如自定义表单元素(<cool-datepicker>)),这是正常的,但是对于网站导航,这是不正常的。 或想象一个<twitter-share>组件,它封装了构建要发送到Twitter的URL的逻辑。 我可以在Svelte上实现它 ,它将在服务器上呈现以下HTML:


 <a target="_blank" noreferrer href="..." class="svelte-1jnfxx"> Tweet this </a> 

换句话说,通常的<a>所有出色的功能。


启用JavaScript后,会逐步进行改进-会打开一个小的弹出窗口,而不是打开新选项卡。 但是即使没有JS,该组件仍然可以正常工作。


对于HTML Web组件,它看起来像这样:


 <twitter-share text="..." url="..." via="..."/> 

...,如果JS被阻止或由于某种原因损坏,或者用户使用的是旧浏览器,则无用且不适合使用。


另外, class="svelte-1jnfxx"为我们提供了没有Shadow DOM的样式封装。 这将我们带到了下一点。


2. CSS in,嗯... JS


如果要使用Shadow DOM封装样式,则需要在<style>插入CSS。 如果要避免加载内容闪烁(FOUC),唯一可行的方法是将CSS作为字符串嵌入JavaScript中,以定义Web组件的其余逻辑。


这与如下的性能改进建议相矛盾:“请少用JavaScript。” 特别是CSS-in-JS社区因不使用CSS CSS文件而受到了很多批评,这里我们再次使用Web组件。


将来,我们将能够使用CSS模块以及可构造样式表来解决此问题。 我们还将有机会通过::theme::part为Shadow DOM的内部设置样式。 但是这里并非没有问题。


3.平台疲劳



对我来说,这是一个痛苦的皇冠-几年来,我一直将这些东西宣传为“未来”,但为了跟上现在,我们不得不在平台上填充一系列不同的功能,从而加剧了浏览器之间的差距。

在撰写本文时,在Chrome的错误跟踪器https://crbug.com上 ,有61,000个已打开的错误,显示出编写现代浏览器的巨大复杂性。


每次我们向平台添加新功能时,都会增加复杂性-潜在地产生新的错误,并降低Chrome拥有新竞争对手的可能性。 这也给鼓励学习这些新功能的开发人员带来了困难(其中一些功能(例如HTML导入或Custom Elements标准的原始版本,尚未在Google之外扎根,现在正在删除中)。


4.亲友


您需要使用多文件来支持较旧的浏览器这一事实并不会助长这种情况的发展。 而且,用Google撰写的有关“可构造样式表”的文章完全没有帮助(嗨,杰森!)。请不要提及此功能仅在Chrome中可用。 (该规范的所有三位作者都为Google工作。Webkit似乎对该标准的某些方面有疑问 )。


5.组成


控制何时需要显示插槽的内容可能很有用。 假设您有 <html-include>用于在可见时加载一些其他内容:


 <p>Toggle the section for more info:</p> <toggled-section> <html-include src="./more-info.html"/> </toggled-section> 

突然之间! 即使我们尚未打开toggled-section ,但浏览器已经请求more-info.html ,以及那里的所有图像和其他资源。


这是因为插槽的内容是预先在Web组件呈现的。 实际上,事实证明,在大多数情况下,您希望延迟渲染插槽的内容。 Svelte v2采用了一种主动式的Redning模型来满足Web标准,但这是带来不便的主要原因-例如,我们无法创建类似于React Router的东西。 在Svelte v3中,我们远离了Web组件的行为,并且从不回头。


不幸的是,这是DOM的基本特征之一。 这带我们去...


6.属性和属性之间的混淆


属性和属性基本上是同一件事,对吗?


 const button = document.createElement('button'); button.hasAttribute('disabled'); // false button.disabled = true; button.hasAttribute('disabled'); // true button.removeAttribute('disabled'); button.disabled; // false 

好吧,几乎:


 typeof button.disabled; // 'boolean' typeof button.getAttribute('disabled'); // 'object' button.disabled = true; typeof button.getAttribute('disabled'); // 'string' 

名称不匹配:


 div = document.createElement('div'); div.setAttribute('class', 'one'); div.className; // 'one' div.className = 'two'; div.getAttribute('class'); // 'two' 

...还有一些根本没有达成共识的建议:


 input = document.createElement('input'); input.getAttribute('value'); // null input.value = 'one'; input.getAttribute('value'); // null input.setAttribute('value', 'two'); input.value; // 'one' 

但是,我们可以处理这些怪癖,字符串格式(HTML)和DOM的交互。 这些功能都有一定数量的记载,因此,如果有时间和耐心,我们至少可以了解它们。


Web组件有所作为。 不再对属性和属性之间的关系做任何保证,并且作为Web组件开发人员,您必须同时支持这两者。 这使我们想到了这样的事情:


 class MyThing extends HTMLElement { static get observedAttributes() { return ['foo', 'bar', 'baz']; } get foo() { return this.getAttribute('foo'); } set foo(value) { this.setAttribute('foo', value); } get bar() { return this.getAttribute('bar'); } set bar(value) { this.setAttribute('bar', value); } get baz() { return this.hasAttribute('baz'); } set baz(value) { if (value) { this.setAttribute('baz', ''); } else { this.removeAttribute('baz'); } } attributeChangedCallback(name, oldValue, newValue) { if (name === 'foo') { // ... } if (name === 'bar') { // ... } if (name === 'baz') { // ... } } } 

您可以执行相反的操作-attributeChangedCallback getter和setter调用。 无论如何,使用它的便利只是令人沮丧。 同时,框架中有一种简单而明确的方式将数据传输到组件。


7.泄漏设计


此项有点含糊,但对我来说奇怪的是attributeChangedCallback只是一个类方法。 您可以执行以下操作:


 const element = document.querySelector('my-thing'); element.attributeChangedCallback('w', 't', 'f'); 

属性没有更改,但是代码的行为就像发生了一样。 当然,在JavaScript中总是有很多危害的方法,但是当我看到以这种方式突出实现细节时,在我看来,设计显然有问题。


8.错误的DOM


好的,我们已经确定DOM不好。 但是,仍然很难夸大制作交互式应用程序的不便之处。


几个月前,我写了一篇文章“写更少的代码”,以说明Svelte如何比React和Vue等框架更有效地编写组件。 没有与香草DOM的比较,但是应该。 简而言之,我们有一个简单的组件<Adder a={1} b={2}/>


 <script> export let a; export let b; </script> <input type="number" bind:value={a}> <input type="number" bind:value={b}> <p>{a} + {b} = {a + b}</p> 

仅此而已。 现在,通过Web组件编写相同的内容:


 class Adder extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); this.shadowRoot.innerHTML = ` <input type="number"> <input type="number"> <p></p> `; this.inputs = this.shadowRoot.querySelectorAll('input'); this.p = this.shadowRoot.querySelector('p'); this.update(); this.inputs[0].addEventListener('input', e => { this.a = +e.target.value; }); this.inputs[1].addEventListener('input', e => { this.b = +e.target.value; }); } static get observedAttributes() { return ['a', 'b']; } get a() { return +this.getAttribute('a'); } set a(value) { this.setAttribute('a', value); } get b() { return +this.getAttribute('b'); } set b(value) { this.setAttribute('b', value); } attributeChangedCallback() { this.update(); } update() { this.inputs[0].value = this.a; this.inputs[1].value = this.b; this.p.textContent = `${this.a} + ${this.b} = ${this.a + this.b}`; } } customElements.define('my-adder', Adder); 

是的


请注意,如果我们同时更改ab ,则将有两个单独的更新。 大多数框架都不会遭受此问题的困扰。


9.全球名称


我不会在很长一段时间内专注于此;足以说在单个共享名称空间中工作的危险早已为人所知并被分解。


10.所有这些问题已经解决。


最大的遗憾是我们已经有了好的组件模型。 我们仍在学习,但是基本任务-通过以面向组件的方式更新DOM将视图与特定状态同步-几年前已经解决。 而且,我们仍在向Web平台添加功能,以赶上库和框架中已有的功能。


由于我们的资源不是无限的,因此花在一项任务上的时间意味着对另一项任务的关注不足。 尽管开发人员普遍漠不关心,但仍在Web组件上花费了大量精力。 将这种精力花在其他事情上,我们可以实现什么?

Source: https://habr.com/ru/post/zh-CN457010/


All Articles