内容简介:原文任何UI框架或库最期望目标之一是帮助我们建立通用的模式或约定。这些约定使UI代码易于共享并为其提供理论基础。很长一段时间,每个框架或库都有自己的实现或UI组件版本。仅在该组件的生态中,这些组件可以实现代码复用。如果给定的UI组件/插件需要在不同的技术依赖中使用,往往由于特定的生态系统限制而成为局限。 例如,如果我编写一个Angular库并想在我的Vue应用程序中使用我的Angular下拉列表,目前还无法直接做到。如果有一个基于任意JavaScript库或框架编写的通用标准组件可以在任何浏览器中用,那就
原文
任何UI框架或库最期望目标之一是帮助我们建立通用的模式或约定。这些约定使UI代码易于共享并为其提供理论基础。很长一段时间,每个框架或库都有自己的实现或UI组件版本。仅在该组件的生态中,这些组件可以实现代码复用。如果给定的UI组件/插件需要在不同的技术依赖中使用,往往由于特定的生态系统限制而成为局限。 例如,如果我编写一个Angular库并想在我的Vue应用程序中使用我的Angular下拉列表,目前还无法直接做到。如果有一个基于任意JavaScript库或框架编写的通用标准组件可以在任何浏览器中用,那就很好了!这是Web Components的愿景。
Web Components是标准化的底层浏览器API的集合,方便我们创建共享的可重用UI组件。在这次介绍中,我们通过构建一个Web组件的例子来介绍其中的一些API。我们的组件是一个简单的计数器组件,当有单击操作时实现数字加减。
为了制作我们的计数器组件,将介绍三种不同的API,Custom Elements,Templates 和Shadow DOM API。首先介绍Custom Elements API。
Custom Elements
Custom Elements API允许我们自定义HTML节点并添加行为给这些节点。使用Custom Elements API,我们还可以扩展原生HTML节点。首先,让我们创建一个最小的自定义节点。
class XCounter extends HTMLElement { constructor() { super(); } connectedCallback() { this.innerHTML = ` <p>Hello From Web Component</p> `; }}customElements.define('x-counter', XCounter);
上面我们用最少的代码量来创建我们的第一个自定义节点。我们继承HTMLElement类来创建自定义节点。在我们的自定义类中,可以定义模板和我们想要的任何行为。在特定生命周期的钩子函数connectedCallback()中,我们将模板赋值给节点的innerHTML属性。构造函数执行完且节点插入DOM之后才会调用connectedCallback()方法。
在我们定义Custom Element类之后,我们需要注册该节点。 customElements.define('x-counter',XCounter);注册节点时,我们传递两个参数。类引用和我们将在HTML中引用的节点的名称。在命名我们的节点时,名称中必须至少有一个破折号。Custom Elements 命名规定至少需要一个破折号,以防止命名与现有HTML节点发生冲突。
此时我们已经创建了Web Component 的基本结构,下面通过添加一个完整的模板来创建我们的计数器组件。
Template and Shadow DOM APIs
const template = document.createElement('template');template.innerHTML = ` <style> button, p { display: inline-block; } </style> <button aria-label="decrement">-</button> <p>0</p> <button aria-label="increment">+</button> `;class XCounter extends HTMLElement { constructor() { super(); this.root = this.attachShadow({ mode: 'open' }); this.root.appendChild(template.content.cloneNode(true)); }}customElements.define('x-counter', XCounter);
Template和Shadow DOM API将允许我们创建高度封装的、高性能的组件。首先我们声明一个新的template节点。使用document.createElement('template');我们可以定义一个独立的原生HTML template。创建template 让我们不必立即将其插入DOM就可以构建HTML节点树。通过使用template,我们可以做到只创建一次template,然后在每次创建组件实例时重复使用它。
我们通过Shadow DOM API而不是之前的innerHTML示例添加我们新创建的template。通过Shadow DOM API将模板添加到我们的组件,我们在构造函数中添加如下代码。
this.root = this.attachShadow({ mode: 'open' });this.root.appendChild(template.content.cloneNode(true));
在节点的构造函数中创建Shadow DOM,从而无需使用connectedCallback钩子函数来创建。 Shadow DOM为我们的组件创造一个高度封装的且隔离的DOM环境。 Shadow DOM会保护我们的HTML不被全局CSS或外部JavaScript污染。例如,在我们上面的模板中,我们有以下CSS:
button, p { display: inline-block;}
在我们的Shadow DOM template中定义样式时,我们的Web组件中的按钮和段落标记将使用内联样式进行设置。样式不会全局泄露,全局CSS样式也不会覆盖我们的template。现在我们已经设置并创建了template,我们需要在按钮上添加一些click事件处理。
Properties
为了与Web Components通信,我们主要通过组件上定义的公共属性来进行数据传递。对于我们的组件,我们将创建一个公共属性value 。
const template = document.createElement('template');template.innerHTML = `<style> button, p { display: inline-block; }</style><button aria-label="decrement">-</button> <p>0</p><button aria-label="increment">+</button>`;class XCounter extends HTMLElement { set value(value) { this._value = value; this.valueElement.innerText = this._value; } get value() { return this._value; } constructor() { super(); this._value = 0; this.root = this.attachShadow({ mode: 'open' }); this.root.appendChild(template.content.cloneNode(true)); this.valueElement = this.root.querySelector('p'); this.incrementButton = this.root.querySelectorAll('button')[1]; this.decrementButton = this.root.querySelectorAll('button')[0]; this.incrementButton .addEventListener('click', (e) => this.value++); this.decrementButton .addEventListener('click', (e) => this.value--); }}customElements.define('x-counter', XCounter);
在我们的组件上,我们为value属性创建了一个get和set方法。使用getter和setter,我们可以触发对template的更新。我们有一个私有的变量value来保留计数器值。在我们的setter中,我们使用下面的代码来更新数值:this.valueElement.innerText = this.value;。
使用我们的公共的 value getter方法,我们可以动态设置计数器的值。例如,我们获得对节点的引用,像其他HTML节点一样与它进行交互。
<x-counter></x-counter>
import 'counter.js';const counter = document.querySelector('x-counter');counter.value = 10;
如您所见,我们可以查询自定义节点并像设置任何其他原生HTML节点一样设置自定义属性。使用我们的组件,我们可以通过输入属性将数据传递给它,但是如果我们希望组件在用户更改计数器值时通知我们怎么办?接下来,我们将介绍自定义事件。
Events
就像任何HTML节点一样,我们的自定义节点可以发出自定义事件供我们监听。在我们例子中,我们想知道用户何时更新了计数器组件的值。我们来看看组件值的更新。
const template = document.createElement('template');template.innerHTML = `<style> button, p { display: inline-block; }</style><button aria-label="decrement">-</button> <p>0</p><button aria-label="increment">+</button>`; class XCounter extends HTMLElement { set value(value) { this._value = value; this.valueElement.innerText = this._value; // trigger our custom event 'valueChange' this.dispatchEvent(new CustomEvent('valueChange', { detail: this._value })); } get value() { return this._value; } constructor() { super(); this._value = 0; this.root = this.attachShadow({ mode: 'open' }); this.root.appendChild(template.content.cloneNode(true)); this.valueElement = this.root.querySelector('p'); this.incrementButton = this.root.querySelectorAll('button')[1]; this.decrementButton = this.root.querySelectorAll('button')[0]; this.incrementButton .addEventListener('click', (e) => this.value++); this.decrementButton .addEventListener('click', (e) => this.value--); }}customElements.define('x-counter', XCounter);
使用我们的组件,我们在value属性的setter方法中添加一个自定义事件。
this.dispatchEvent(new CustomEvent('valueChange', { detail: this._value }));
我们可以发送自定义事件。自定义事件类有两个参数。第一个参数是事件的名称;第二个参数是我们想要传回的数据。通常会传递包含已更改数据detail属性的对象。当我们的自定义事件发出时,我们能够监听事件,同时获取事件值以及节点触发事件的详细信息。为了监听事件,我们可以像标准HTML节点一样创建事件监听器。
import 'counter.js';const counter = document.querySelector('x-counter');counter.value = 10;counter.addEventListener('valueChange', v => console.log(v));
在我们的代码中,我们可以监听自定义valueChange事件,在这里我们记录该值。
Attributes
有时,通过特性而不是属性将信息传递给组件也很方便。例如,我们可能想要传递一个初始值给我们的计数器。
<x-counter value="5"></x-counter>
为此,我们需要为组件添加一些额外的代码。
const template = document.createElement('template');template.innerHTML = `<style> button, p { display: inline-block; }</style><button aria-label="decrement">-</button> <p>0</p><button aria-label="increment">+</button>`; class XCounter extends HTMLElement { // Attributes we care about getting values from. static get observedAttributes() { return ['value']; } set value(value) { this._value = value; this.valueElement.innerText = this._value; this.dispatchEvent(new CustomEvent('valueChange', { detail: this._value })); } get value() { return this._value; } constructor() { super(); this._value = 0; this.root = this.attachShadow({ mode: 'open' }); this.root.appendChild(template.content.cloneNode(true)); this.valueElement = this.root.querySelector('p'); this.incrementButton = this.root.querySelectorAll('button')[1]; this.decrementButton = this.root.querySelectorAll('button')[0]; this.incrementButton .addEventListener('click', (e) => this.value++); this.decrementButton .addEventListener('click', (e) => this.value--); } // Lifecycle hook called when a observed attribute changes attributeChangedCallback(attrName, oldValue, newValue) { if (attrName === 'value') { this.value = parseInt(newValue, 10); } }}customElements.define('x-counter', XCounter);
在我们的组件上,我们必须添加两个部分。首先是我们希望在更改时收到通知的特性列表。这是Web Components所需的性能优化。
static get observedAttributes() { return ['value'];}
在我们的组件上,我们还添加了一个生命周期中新的钩子函数。当HTML特性有更新时,就会调用attributeChangedCallback()
attributeChangedCallback(attrName, oldValue, newValue) { if (attrName === 'value') { this.value = parseInt(newValue, 10); }}
对于Web通信最佳实践,最好使用自定义属性而不是自定义特性。属性更灵活,可以处理复杂的数据类型,如对象或数组。使用属性时,因为HTML的限制所有值都被当做String类型。自定义特性虽然很有用,但始终从属性开始,并根据需要添加特性。如果使用Web Component创作工具(如StencilJS),该 工具 会自动连接属性中的特性并使其保持同步。
总结
使用Web Components,我们可以创建可重用的Web UI组件库。Web components在Chrome,Safari中已经支持,很快Firefox便会支持。通过polyfill,我们还可以支持Edge(现在正在实现Web Component API)和IE11。大多数现代JavaScript框架也支持Web Components,您可以在custom-elements-everywhere.com上看到完全支持。
想要深入了解有关Web Components的更多信息?查看我的早期发行书Web Component Essentials!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Ruby on Rails社区网站开发
布拉德伯纳 / 柳靖 / 2008-10 / 55.00元
《Ruby on Rails社区网站开发》全面探讨创建完整社区网站的开发过程。首先介绍开发一个内容简单的管理系统,之后逐渐添加新特性,以创建更完整的、使用Ruby on Rails 的Web 2.0 社区网站。还给出了开发和测试中的一些建议和提示,同时指导如何使网站更生动以及维护得更好。《Ruby on Rails社区网站开发》也探讨了如何与Flickr 、Google Maps 等其他平台集成,......一起来看看 《Ruby on Rails社区网站开发》 这本书的介绍吧!