前 言Web Components 的Api、高级用法和相关工具很多,本文并不是全面的教程,只是一个简单的介绍和演示,感兴趣的话可以深入了解
一、发展历程
- 出现:2011 年,html templates 的概念首次提出,同时 Google 开始开发 Polymer 项目,Web Components 的概念也由 Google 工程师 Alex Russell 在 Fronteers 会议上首次提出
- 发展:
- 2013 年,Google 发布了 Polymer 框架,为 Web Components 提供了支持,同年 W3C 开始制定 Web Components 标准
- 2014 年,HTML Templates 被添加到 Web Components 规范草案中
- 2015 年,Custom Elements 和 Shadow DOM 被添加到 Web Components 规范草案中,同年发布了 Web Components 1.0 草案
- 标准化:2017 年,Web Components 1.0 正式标准发布。2018 年,Web Components 规范正式成为 W3C 的推荐标准,Chrome 63 正式支持 Web Components 技术
二、Web Component的定义
MDN中的解释:Web Component 是一套不同的技术,允许你创建可重用的定制元素(它们的功能封装在你的代码之外)并且在你的 web 应用中使用它们。
在现代流行的前端开发框架
vue、react等中,基本都使用了组件化思想,由此也诞生了各式各样的UI组件库,其出现主要目的就是为了代码复用,提高开发效率。通俗点讲,
Web Component就是浏览器支持的原生组件 ,允许你创建可以复用的自定义HTML元素,并且可以像普通HTML标签那样直接使用。
它主要包括下面三个核心技术:
Cutsom Element
创建
自定义元素需要定义一个类,所有<my-custom-element></my-custom-element>都将会是这个类的实例:
class MyCustomElement extends HTMLElement { constructor() { super(); // 在这里可以进行元素的初始化操作,比如添加子元素、设置属性等 this.textContent = '这是一个自定义元素'; }}// 使用window.customElements.define()告诉浏览器这个自定义元素和哪个类关联customElements.define('my-custom-element', MyCustomElement);TIP上面这个自定义元素(Web Component)继承了
HTMLElement,所以它将可以访问各种类方法,比如,组件的生命周期回调。
自定义元素的内容
上面我们只是创建了自定义元素,其中还没什么实质内容,你可以通过任意DOM操作添加自定义内容:
class MyCustomElement extends HTMLElement { constructor() { super();
var image = document.createElement('img'); image.src = 'xxx'; image.classList.add('image');
var container = document.createElement('div'); container.classList.add('container');
var name = document.createElement('p'); name.classList.add('name'); name.innerText = 'User Name';
var email = document.createElement('p'); email.classList.add('email'); email.innerText = 'yourmail@some-email.com';
var button = document.createElement('button'); button.classList.add('button'); button.innerText = 'Follow';
container.append(name, email, button); this.append(image, container); }}自定义参数
<MyCustomElement image="https://semantic-ui.com/images/avatar2/large/kristy.png" name="User Name" email="yourmail@some-email.com"></MyCustomElement>class UserCard extends HTMLElement { constructor() { super();
var templateElem = document.getElementById('userCardTemplate'); var content = templateElem.content.cloneNode(true); content.querySelector('img').setAttribute('src', this.getAttribute('image')); content.querySelector('.container>.name').innerText = this.getAttribute('name'); content.querySelector('.container>.email').innerText = this.getAttribute('email'); this.appendChild(content); }}window.customElements.define('my-custom-element', MyCustomElement);Shadow DOM
一种封装技术。简单来说,它允许将一个DOM Tree(包含元素、样式等)隐藏在一个’影子’中,与主文档的DOM树隔离,以此达到防止样式和脚本冲突的目的。
CAUTION需要注意的是,这并不是虚拟DOM(性能优化)!
一个简单的Shadow DOM示例:
class MyElementWithShadowDOM extends HTMLElement { constructor() { super(); // 创建Shadow DOM const shadow = this.attachShadow({mode: 'open'}); const p = document.createElement('p'); p.textContent = '这是Shadow DOM中的内容'; shadow.appendChild(p); }}customElements.define('my-element-with-shadow-dom', MyElementWithShadowDOM);HTML Templates
上面我们通过DOM操作编写组件结构很麻烦,Web Components API提供了
template来简化这个过程。它本质上就是一种定义HTML片段的方式(如果你熟悉Vue,应该对这种写法很熟悉)。
使用的template形式多种多样,你可以将其插入HTML,然后通过js脚本获取:
<template id="my-template"> <style> /* :host伪类可指代自定义元素本身 */ :host {} /* ... ... */ </style> <div> <p>这是模板中的内容</p> </div></template>const template = document.getElementById('my-template');const content = template.content.cloneNode(true);document.body.appendChild(content);但通常,为了更方便维护,你可能不希望template与js分离,那么你也可以在js中定义template:
const template = document.createElement('template')template.innerHTML = /*html*/ `<style>... ...</style>
<div class="custom-card"></div>`class CustomCard extends HTMLElement { private _shadowRoot: ShadowRoot constructor() { super()
this._shadowRoot = this.attachShadow({ mode: 'closed' }) const content = template.content.cloneNode(true) this._shadowRoot.appendChild(content) }}三、优劣分析
Web Components的优点
- 纯原生:现代浏览器(Chrome, FireFox, Safari等)原生支持,不需要引入额外的库。因此,它可以在任何原生JS或者JS框架中使用,具有良好的兼容性
- 封装性好:通过
Shadow DOM,能够很好的封装内部样式的结构 - 可复用等
Web Components的不足
- 生态和工具支持较弱:目前的生态系统还不够丰富,编写简单的组件尚可,对于复杂组件,需要开发者自己实现更多的功能(例如状态管理,数据响应式等),效率低、开发成本较高,也不支持SSR等
四、常见应用场景
- UI组件库:可以实现一个通用的,跨框架,可在多个项目中使用的UI组件库
- 第三方组件:可以创建轻松嵌入到其他应用的小组件(例如:社交媒体的分享按钮、广告组件、视频播放器、评论窗口等)。本博客的文章目录,友链卡片,追番列表等都使用了
Web Components技术 - 微前端
- 模块划分与独立部署:在微前端场景中,不同的团队可以开发和部署各自的前端应用,这些应用可以被看作是大型应用的 “微模块”。
Web Components可以作为这些微模块的基础构建单元 - 集成与通信:
Web Components能够方便地集成到不同的宿主应用中。它们可以通过自定义事件等方式进行通信
- 模块划分与独立部署:在微前端场景中,不同的团队可以开发和部署各自的前端应用,这些应用可以被看作是大型应用的 “微模块”。
五、编写一个简单的Web Component
以本博客的友链卡片组件做一个最简单的示例:
import { loadImage } from '@utils/file-utils'
const template = document.createElement('template')template.innerHTML = /*html*/ `<style> ... ...</style>
<div class="info-card"> <span class="tag"></span> <a class="link" target="_blank"> <img class="avatar"> <div class="info"> <div class="name"></div> <div class="desc"></div> </div> </a></div>`
class InfoCard extends HTMLElement { private _shadowRoot: ShadowRoot private tag: string | null = null private link = '' private avatar = '' private name = '' private desc = '' constructor() { super()
this._shadowRoot = this.attachShadow({ mode: 'closed' }) const content = template.content.cloneNode(true)
this._shadowRoot.appendChild(content) }
static get observedAttributes() { return ['tag', 'link', 'avatar', 'name', 'desc'] }
attributeChangedCallback( name: 'tag' | 'link' | 'avatar' | 'name' | 'desc', oldVal: string | null, newVal: string | null, ) { if (newVal !== null) { this[name] = newVal } this.render() }
connectedCallback() { this.render() }
async render() { const tagElement = this._shadowRoot.querySelector('.tag') as HTMLSpanElement const linkElement = this._shadowRoot.querySelector( '.link', ) as HTMLAnchorElement const avatarElement = this._shadowRoot.querySelector( '.avatar', ) as HTMLImageElement const nameElement = this._shadowRoot.querySelector( '.name', ) as HTMLDivElement const descElement = this._shadowRoot.querySelector( '.desc', ) as HTMLDivElement
if (tagElement) { if (this.tag === null) { tagElement.style.display = 'none' } tagElement.textContent = this.tag } if (linkElement) { linkElement.href = this.link linkElement.title = this.name } if (avatarElement) { avatarElement.src = await loadImage(this.avatar, { defaultImageUrl: '/404.gif', }) avatarElement.setAttribute('alt', this.name) } if (nameElement) { nameElement.textContent = this.name } if (descElement) { descElement.textContent = this.desc } }}
customElements.define('info-card', InfoCard)六、相关推荐
工具
Stencil
2019 年6月正式发布第一版,官方定义是一个Web Component编译器,lonic 团队开发,把现在流行的虚拟 dom、异步渲染、响应式、JSX 等概念都做了支持,并且自己只是一个构建时工具。用 Stencil 开发的框架可以独立运行、也可以运行在主流框架
Lit
Lit 是由 Google 制作的一个简单的库,用于创建 Web Components。Lit 提供了一个基础类(LitElement)来帮助开发者创建 Web Components,并使用一个叫做 lit-html 的模板引擎来定义组件的 HTML 结构
Fast
FAST 微软2020 年发布的标准化解决方案,可以用来创建组件和设计系统。组件核心是基于 Web Components 做到框架无关,帮助开发者快速构建高性能的 Web 用户界面
Open-wc
Open-WC 提供了一套建议和工具集,用于帮助开发者创建 Web Components 和 Web 应用
文章
如果你想了解更多高级用法和详细信息,除了查阅官方文档之外,也可以阅读一下这些文章: