大家好,很高兴又见面了,我是"高级前端分享",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!
今天给大家带来的主题是Web Component,话不多说,直接进入正题。
1.前言
项目受益于使用具有单一职责的简单、独立的模块。模块化代码被封装,因此无需担心实现。只要您知道在给定一组输入时模块将输出什么,而不一定需要了解它是如何实现该目标的。
将模块化概念应用于单一编程语言很简单,但 Web 开发需要多种技术的组合。浏览器解析 HTML、 CSS 和 JavaScript 以呈现页面的内容、样式和功能。但是三者的结合可能会遇到各种问题:
- 相关代码可以拆分为多个文件
- 全局样式和 JavaScript 对象会以意想不到的方式相互干扰。
- 除了上面的问题之外,语言运行时、框架、数据库和服务器上使用的其他依赖项也会遇到这些问题
2.什么是 Web Component?
Web Component 是一种创建可在任何页面上重复使用的、高度封装、单一职责代码块的方法。比如 HTML
Web Component 虽然提供了样式和功能,但可以使用各种属性和 JavaScript API 调用进行修改。可以将任意数量的
如果您需要自己的自定义功能怎么办?例如,显示页面字数的元素?没有 HTML
- 您必须学习如何使用该框架并随着它的发展更新您的代码。
- 为一个框架编写的组件很少与另一个框架兼容。
- 框架的更迭
- 标准的 Web Components 可以添加浏览器功能,这是仅靠 JavaScript 难以实现的(例如 Shadow DOM)
幸运的是,库和框架中引入的流行概念通常会进入 Web 标准,虽然花了一些时间,但 Web Component 已经到来。
3.Web Component 发展史?
标准 Web Component 的概念由 Alex Russell 在 年的 Fronteers Conference 上首次引入。两年后谷歌的 Polymer 库(基于当前提议的 polyfill)问世,但早期的实现直到 年才出现在 Chrome 和 Safari 中。
浏览器供应商花时间协商细节,但 Web Components 于 年添加到 Firefox 中,并于 年添加到 Edge(当时微软切换到 Chromium 引擎)。可以理解的是,很少有开发人员愿意或能够采用 Web Components,最终通过稳定的 API 达到了良好的浏览器支持水平。
即使您现在还不愿意放弃您最喜欢的 React、Vue 等框架,但是 Web Components 与每个框架都兼容,并且 API 将在未来几年内得到更多支持。您可以通过文末参考文献查看 Web Components 的各种组件库。
本教程完整介绍了在没有 JavaScript 框架的情况下编写 Web Component。您将了解它们是什么以及如何将它们用于您的 Web 项目。当然,前提是您需要了解一些 HTML5、CSS 和 JavaScript 知识。
4.Web Component 入门?
一个 Web Component 例子
Web Component 是自定义 HTML 元素,例如
您必须定义一个 ES2015 类来控制该元素。它可以任意命名,但 让我们从 HelloWorld 开始。这个类必须扩展 HTMLElement 接口,它表示每个 HTML 元素的默认属性和方法。
注意: Firefox 允许您扩展特定的 HTML 元素,例如 HTMLParagraphElement、HTMLImageElement 或 HTMLButtonElement。这在其他浏览器中不受支持,并且不允许您创建 Shadow DOM。
该类需要一个名为 connectedCallback()的方法,该方法在元素添加到文档时调用:
class HelloWorld extends HTMLElement {
// connect component
connectedCallback() {
this.textContent = 'Hello World!';
}
}
在此示例中,元素的文本设置为“Hello World”。该类必须在 CustomElementRegistry 中注册才能将其定义为特定元素的处理程序:
customElements.define('hello-world', HelloWorld);
当您的 JavaScript 被加载时,浏览器现在将该
// web component class HelloWorld extends HTMLElement { // connect component
connectedCallback() { this.textContent = 'Hello World!'; } } // register
component customElements.define( 'hello-world', HelloWorld );
CSS 内容如下:
body {
font-family: sans-serif;
}
/*这个组件可以像任何其他元素一样在 CSS 中设置样式: */
hello-world {
font-weight: bold;
color: red;
}
HTML 内容如下:
A new
Web Component...
一个 Web Component 添加属性
这个组件功能比较弱,因为无论如何都会输出相同的文本。与任何其他元素一样,我们可以添加 HTML 属性:
这可以用于覆盖文本输。为此,您可以向 HelloWorld 类添加一个 constructor()函数,该函数在创建每个对象时运行。它必须满足以下两个条件:
- 调用 super()方法来初始化父 HTMLElement
-进行其他初始化。在本例中,将定义一个默认值为“World”的名称属性。
class HelloWorld extends HTMLElement {
constructor() {
super();
this.name = 'World';
}
// more code...
因为组件只关心 name 属性,一个静态的 observedAttributes()属性应该返回一个属性数组来观察:
// component attributes
static get observedAttributes() {
return ['name'];
}
当属性在 HTML 中定义或使用 JavaScript 更改时,将调用 attributeChangedCallback()方法,它传递了属性名称、旧值和新值:
// attribute change
attributeChangedCallback(property, oldValue, newValue) {
if (oldValue === newValue) return;
this[ property ] = newValue;
}
在此示例中,只会更新名称属性,但您可以根据需要添加其他属性。最后,您需要调整 connectedCallback()方法中的消息:
// connect component
connectedCallback() {
this.textContent = `Hello ${ this.name }!`;
}
Web Component 生命周期方法
在 Web Component 状态的整个生命周期中,浏览器会自动调用六个方法。此处提供了完整列表,尽管您已经在上面的示例中看到了前四个:
- 构造函数():它在组件首次初始化时调用。它必须调用 super()并且可以设置任何默认值或执行其他预渲染过程
- 静态观察属性():返回浏览器将观察到的属性数组。
- attributeChangedCallback(propertyName, oldValue, newValue):每当观察到属性发生更改时调用。那些在 HTML 中定义的属性会被立即传递,但 JavaScript 可以修改它们。
document.querySelector('hello-world').setAttribute('name', 'Everyone');
发生这种情况时,该方法可能需要触发重新渲染。
- 连接回调():当 Web Component 附加到文档对象模型时调用此函数,它应该运行任何需要的渲染。
- 断开连接回调():当从文档对象模型中删除 Web Component 时调用它。如果您需要清理,例如删除存储的状态或中止 Ajax 请求,这可能很有用。
- 采用回调():当 Web 组件从一个文档移动到另一个文档时调用此函数。
Web Component 与其他元素交互
Web Components 提供了一些在 JavaScript 框架中找不到的独特功能。
影子 DOM
虽然上面构建的 Web Component 可以正常工作,但它不能免受外部干扰,CSS 或 JavaScript 可以对其进行修改。同样,您为组件定义的样式可能会泄露并影响其他组件。
Shadow DOM 通过将一个单独的 DOM 附加到 Web 组件来解决这个封装问题:
const shadow = this.attachShadow({ mode: 'closed' });
模式可以是:
- “open”:外部页面中的 JavaScript 可以访问 Shadow DOM(使用 Element.shadowRoot)
- “close” :只能在 Web Component 中访问 Shadow DOM
Shadow DOM 可以像任何其他 DOM 元素一样被操作:
connectedCallback() {
const shadow = this.attachShadow({ mode: 'closed' });
shadow.innerHTML = `
Hello ${ this.name }!
`;
}
该组件现在在元素内呈现“Hello”文本 并为其设置样式。它不能被组件外部的 JavaScript 或 CSS 修改,尽管一些样式如字体和颜色是从页面继承的,因为它们没有明确定义。
此 Web 组件范围内的样式不会影响页面上的其他段落甚至其他
:host {
transform: rotate(180deg);
}
您还可以设置元素使用特定类时要应用的样式,例如
:host(.rotate90) {
transform: rotate(90deg);
}
HTML 模板
对于更复杂的 Web Component,在脚本中定义 HTML 可能变得不切实际。模板允许您在页面中定义您的 Web Component 可以使用的 HTML 块。这有几个好处:
- 您可以调整 HTML 代码,而无需在 JavaScript 中重写字符串
- 无需为每种类型创建单独的 JavaScript 类,即可自定义组件
- 在 HTML 中定义 HTML 更容易 — 并且可以在组件呈现之前在服务器或客户端上对其进行修改。
模板在标签中定义,分配一个 ID ,这样您就可以在组件类中引用它。本示例分三段显示“Hello”消息:
Web Component 类可以访问此模板、获取其内容并克隆元素以确保您在使用它的任何地方都创建一个唯一的 DOM 片段:
const template = document.getElementById('hello-world').content.cloneNode(true);
可以修改 DOM 并将其直接添加到 Shadow DOM 中:
connectedCallback() {
const shadow = this.attachShadow({ mode: 'closed' }),
template = document.getElementById('hello-world').content.cloneNode(true),
hwMsg = `Hello ${ this.name }`;
Array.from( template.querySelectorAll('.hw-text') )
.forEach( n => n.textContent = hwMsg );
shadow.append( template );
}
模板插槽
插槽允许您自定义模板。假设您想要使用您的标题中。你可以写这段代码:
Hello Default!
您可以选择添加其他元素,例如另一个段落:
Hello Default!
This text will become part of the component.
现在可以在您的模板中实现插槽:
设置为“msgtext”(the 没有分配插槽名称,但它在下一个可用的未命名中使用)的元素槽属性被插入到
Hello Default!
This text will become part of the component.
现实中并非如此简单。
connectedCallback() {
const
shadow = this.attachShadow({ mode: 'closed' }),
hwMsg = `Hello ${ this.name }`;
// append shadow DOM
shadow.append(
document.getElementById('hello-world').content.cloneNode(true)
);
// find all slots with a hw-text class
Array.from( shadow.querySelectorAll('slot.hw-text') )
// update first assignedNode in slot
.forEach( n => n.assignedNodes()[0].textContent = hwMsg );
}
此外,您不能直接为插入的元素设置样式,尽管您可以在 Web Component 中定位特定的插槽:
模板插槽有点别扭,但一个好处是如果 JavaScript 无法运行,您的内容将被显示。此代码显示了一个默认标题和段落,它们仅在 Web Component 类成功执行时才被替换:
Hello Default!
This text will become part of the component.
声明式 Shadow DOM
上面的示例使用 JavaScript 构造了一个 Shadow DOM。这仍然是唯一的选择,但 Chrome 正在开发一个实验性的声明式 Shadow DOM 。这允许服务器端呈现并避免任何布局偏移或无样式内容的闪烁。
HTML 解析器检测到以下代码,它会创建一个与您在上一节中创建的相同的 Shadow DOM(您需要根据需要更新消息):
Hello Default!
This text will become part of the component.
该功能在任何浏览器中都不可用,并且不能保证 Firefox 或 Safari 会支持它。您可以找到有关声明性 Shadow DOM 的更多信息,并且 polyfill 很简单,但请注意实现可能会发生变化。
影子 DOM 事件
您的 Web Component 可以将事件附加到 Shadow DOM 中的任何元素,就像您在页面 DOM 中一样,例如监听所有内部子元素的点击事件:
shadow.addEventListener('click', (e) => {
// do something
});
注意:除非你 stopPropagation,否则事件将冒泡到页面 DOM 中,但事件将被重定向。因此,它似乎来自您的自定义元素而不是其中的元素。
5.在框架中使用 Web Component?
您创建的任何 Web Component 都可以在所有 JavaScript 框架中运行。他们都不知道也不关心 HTML 元素。比如您的
import React from 'react';
import ReactDOM from 'react-dom';
import from './hello-world.js';
function MyPage() {
return (
<>
>
);
}
ReactDOM.render( , document.getElementById('root'));
但是:
- React 只能将原始数据类型传递给 HTML 属性(而不是数组或对象)
- React 无法侦听 Web Component 事件,因此您必须手动附加自己的处理程序。
6.WebComponent 的批评和问题?
Web Components 有了显着改进,但某些方面可能难以管理。
写法问题
Web Component 的样式会带来一些挑战,尤其是当您想要覆盖范围样式时。但是,依然有很多解决方案:
- 避免使用 Shadow DOM。您可以将内容直接附加到您的自定义元素,尽管任何其他 JavaScript 都可能意外或恶意地更改它。
- 使用:host 类。正如我们在上面看到的,作用域 CSS 可以在将类应用于自定义元素时应用特定样式。
- 查看 CSS 自定义属性(变量)。自定义属性级联到 Web Component 中,因此,如果您的元素使用 var(--my-color),您可以--my-color 在外部容器中设置(例如:root),它就会被使用。
- 利用阴影部分。新的::part() 选择器可以为具有 part 属性的内部组件设置样式,即组件
内部
可以使用 selector 设置样式 hello-world::part(heading)。 - 传入一串样式。您可以将它们作为属性传递以在
`;
// monitor input values
shadow.querySelector('input').addEventListener('input', e => {
this.setValue(e.target.value);
});
}
您现在可以使用此 Web Component 创建 HTML 表单,它的作用与其他表单字段类似:
它虽然有效,但不可否认感觉有点令人费解。
7.总结
在 JavaScript 框架功能不断完善的时候,Web Component 也一直在努力获得认可。如果您经常使用 React、Vue.js 或 Angular,Web Component 可能看起来复杂而笨拙,尤其是当您缺少数据绑定和状态管理等功能时。
Web Component 确实有一些问题亟待解决,但 Web Components 的未来是光明的。它们与框架无关、轻量级、快速,并且可以实现单独使用 JavaScript 无法实现的功能。
十年前,很少有人会在没有 jQuery 的情况下开发网站,浏览器开发商吸纳了优秀的部分并添加到本机实现(例如 querySelector)。JavaScript 框架也在渐渐发生变化, Web Components 是试探性的第一步。
参考资料
https://kinsta.com/blog/web-components/#constructor
https://www.webcomponents.org/
https://component.gallery/
https://genericcomponents.netlify.app/
https://github.com/mdn/web-components-examples
https://github.com/davatron5000/awesome-standalones
https://github.com/scottaohara/accessible_components
https://kickstand-ui.com/