使用纯粹的JS构建 Web Component

简介: Web Component 出现有一阵子了。 Google 费了很大力气去推动它更广泛的应用,但是除 Opera 和 Chrome 以外的多数主流浏览器对它的支持仍然不够理想。
原文链接: https://ayushgp.github.io/html-web-components-using-vanilla-js
译者:阿里云 - 也树

但是通过 polyfill,你可以从现在开始构建你自己的 Web Component,你可以在这里找到相关支持:https://www.webcomponents.org/polyfills

在这篇文章中,我会演示如何创建带有样式,拥有交互功能并且在各自文件中优雅组织的 HTML 标签。

介绍

Web Component 是一系列 web 平台的 API,它们可以允许你创建全新可定制、可重用并且封装的 HTML 标签,从而在普通网页及 web 应用中使用。

定制的组件基于 Web Component 标准构建,可以在现在浏览器上使用,也可以和任意与 HTML 交互的 JavaScript 库和框架配合使用。

用于支持 Web Component 的特性正逐渐加入 HTML 和 DOM 的规范,web 开发者使用封装好样式和定制行为的新元素来拓展 HTML 会变得轻而易举。

它赋予了仅仅使用纯粹的JS/HTML/CSS就可以创建可重用组件的能力。如果 HTML 不能满足需求,我们可以创建一个可以满足需求的 Web Component。

举个例子,你的用户数据和一个 ID 有关,你希望有一个可以填入用户 ID 并且可以获取相应数据的组件。HTML 可能是下面这个样子:

<user-card user-id="1"></user-card>

这是一个 Web Component 最基本的应用。下面的教程将会聚焦在如何构建这个用户卡片组件。

Web Component 的四个核心概念

HTML 和 DOM 标准定义了四种新的标准来帮助定义 Web Component。这些标准如下:

  1. 定制元素(Custom Elements): web 开发者可以通过定制元素创建新的 HTML 标签、增强已有的 HTML 标签或是二次开发其它开发者已经完成的组件。这个 API 是 Web Component 的基石。
  2. HTML 模板(HTML Templates): HTML 模板定义了新的元素,描述一个基于 DOM 标准用于客户端模板的途径。模板允许你声明标记片段,它们可以被解析为 HTML。这些片段在页面开始加载时不会被用到,之后运行时会被实例化。
  3. Shadow DOM: Shadow DOM 被设计为构建基于组件的应用的一个工具。它可以解决 web 开发的一些常见问题,比如允许你把组件的 DOM 和作用域隔离开,并且简化 CSS 等等。
  4. HTML 引用(HTML Imports): HTML 模板(HTML Templates)允许你创建新的模板,同样的,HTML 引用(HTML imports)允许你从不同的文件中引入这些模板。通过独立的HTML文件管理组件,可以帮助你更好的组织代码。

定义定制元素

我们首先需要声明一个类,定义元素如何表现。这个类需要继承 HTMLElement 类,但让我们先绕过这部分,先来讨论定制元素的生命周期方法。你可以使用下面的生命周期回调函数:

  • connectedCallback — 每当元素插入 DOM 时被触发。
  • disconnectedCallback — 每当元素从 DOM 中移除时被触发。
  • attributeChangedCallback — 当元素上的属性被添加、移除、更新或取代时被触发。

UserCard 文件夹下创建 UserCard.js:

class UserCard extends HTMLElement {
  constructor() {
    super();
    this.addEventListener('click', e => {
      this.toggleCard();
    });
  }

  toggleCard() {
    console.log("Element was clicked!");
  }
}

customElements.define('user-card', UserCard);

这个例子里我们已经创建了一个定义了定制元素行为的类。customElements.define('user-card', UserCard); 函数调用告知 DOM 我们已经创建了一个新的定制元素叫 user-card,它的行为被 UserCard 类定义。现在可以在我们的 HTML 里使用 user-card 元素了。

我们会用到 https://jsonplaceholder.typicode.com/ 的 API 来创建我们的用户卡片。下面是数据的样例:

{
  id: 1,
  name: "Leanne Graham",
  username: "Bret",
  email: "Sincere@april.biz",
  address: {
    street: "Kulas Light",
    suite: "Apt. 556",
    city: "Gwenborough",
    zipcode: "92998-3874",
    geo: {
      lat: "-37.3159",
      lng: "81.1496"
    }
  },
  phone: "1-770-736-8031 x56442",
  website: "hildegard.org"
}

创建模板

现在,让我们创建一个将在屏幕上渲染的模板。创建一个名为 UserCard.html 的新文件,内容如下:

<template id="user-card-template">
  <div>
    <h2>
      <span></span> (
      <span></span>)
    </h2>
    <p>Website: <a></a></p>
    <div>
      <p></p>
    </div>
    <button class="card__details-btn">More Details</button>
  </div>
</template>
<script src="/UserCard/UserCard.js"></script>

注意:我们在类名前加了一个 card__ 前缀。在较早版本的浏览器中,我们不能使用 shadow DOM 来隔离组件 DOM。这样当我们为组件编写样式时,可以避免意外的样式覆盖。

编写样式

我们创建好了卡片的模板,现在来用 CSS 装饰它。创建一个 UserCard.css 文件,内容如下:

.card__user-card-container {
  text-align: center;
  display: inline-block;
  border-radius: 5px;
  border: 1px solid grey;
  font-family: Helvetica;
  margin: 3px;
  width: 30%;
}

.card__user-card-container:hover {
  box-shadow: 3px 3px 3px;
}

.card__hidden-content {
  display: none;
}

.card__details-btn {
  background-color: #dedede;
  padding: 6px;
  margin-bottom: 8px;
}

现在,在 UserCard.html 文件的最前面引入这个 CSS 文件:

<link rel="stylesheet" href="/UserCard/UserCard.css">

样式已经就绪,接下来可以继续完善我们组件的功能。

connectedCallback

现在我们需要定义创建元素并且添加到 DOM 中会发生什么。注意这里 constructorconnectedCallback 方法的区别。

constructor 方法是元素被实例化时调用,而 connectedCallback 方法是每次元素插入 DOM 时被调用。connectedCallback 方法在执行初始化代码时是很有用的,比如获取数据或渲染。

小贴士: 在 UserCard.js 的顶部,定义一个常量 currentDocument。它在被引入的 HTML 脚本中是必要的,允许这些脚本有途径操作引入模板的 DOM。像下面这样定义:

const currentDocument = document.currentScript.ownerDocument;

接下来定义我们的 connectedCallback 方法:

// 元素插入 DOM 时调用
connectedCallback() {
  const shadowRoot = this.attachShadow({mode: 'open'});

  // 选取模板并且克隆它。最终将克隆后的节点添加到 shadowDOM 的根节点。
  // 当前文档需要被定义从而获取引入 HTML 的 DOM 权限。
  const template = currentDocument.querySelector('#user-card-template');
  const instance = template.content.cloneNode(true);
  shadowRoot.appendChild(instance);

  // 从元素中选取 user-id 属性
  // 注意我们要像这样指定卡片: 
  // <user-card user-id="1"></user-card>
  const userId = this.getAttribute('user-id');

  // 根据 user ID 获取数据,并且使用返回的数据渲染
  fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
      .then((response) => response.text())
      .then((responseText) => {
          this.render(JSON.parse(responseText));
      })
      .catch((error) => {
          console.error(error);
      });
}

渲染用户数据

我们已经定义好了 connectedCallback 方法,并且把克隆好的模板绑定到了 shadow root 上。现在我们需要填充模板内容,然后在 fetch 方法获取数据后触发 render 方法。下面来编写 rendertoggleCard 方法。

render(userData) {
  // 使用操作 DOM 的 API 来填充卡片的不同区域
  // 组件的所有元素都存在于 shadow dom 中,所以我们使用了 this.shadowRoot 这个属性来获取 DOM
  // DOM 只可以在这个子树种被查找到
  this.shadowRoot.querySelector('.card__full-name').innerHTML = userData.name;
  this.shadowRoot.querySelector('.card__user-name').innerHTML = userData.username;
  this.shadowRoot.querySelector('.card__website').innerHTML = userData.website;
  this.shadowRoot.querySelector('.card__address').innerHTML = `<h4>Address</h4>
    ${userData.address.suite}, <br />
    ${userData.address.street},<br />
    ${userData.address.city},<br />
    Zipcode: ${userData.address.zipcode}`
}

toggleCard() {
  let elem = this.shadowRoot.querySelector('.card__hidden-content');
  let btn = this.shadowRoot.querySelector('.card__details-btn');
  btn.innerHTML = elem.style.display == 'none' ? 'Less Details' : 'More Details';
  elem.style.display = elem.style.display == 'none' ? 'block' : 'none';
}

既然组件已经完成,我们就可以把它用在任意项目中了。为了继续教程,我们需要创建一个 index.html 文件,然后写入下面的代码:

<html>

<head>
  <title>Web Component</title>
</head>

<body>
  <user-card user-id="1"></user-card>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.0.14/webcomponents-hi.js"></script>
  <link rel="import" href="./UserCard/UserCard.html">
</body>

</html>

因为并不是所有浏览器都支持 Web Component,我们需要引入 webcomponents.js 这个文件。注意我们用到 HTML 引用语句来引入我们的组件。

为了运行这些代码,你需要创建一个静态文件服务器。如果你不清楚如何创建,你可以使用像 static-server 或者 json-server 这样的简易静态服务。教程里,我们安装 static-server:

$ npm install -g static-server

接着在你的项目目录下,使用下面的命令运行服务器:

$ static-server

打开你的浏览器并访问localhost:3000,你就可以看到我们刚刚创建的组件了。

小贴士和技巧

还有很多关于 Web Component 的东西没有在这篇短文中写到,我想简单的陈述一些开发 Web Component 的小贴士和技巧。

组件的命名

  • 定制元素的名称必须包含一个短横线。所以 <my-tabs><my-amazing-website> 是合法的名称, 而<foo><foo_bar> 不行。
  • 在 HTML 添加新标签时需要确保向前兼容,不能重复注册同一个标签。
  • 定制元素标签不能是自闭合的,因为 HTML 只允许一部分元素可以自闭合。需要写成像 <app-drawer></app-drawer> 这样的闭合标签形式。

拓展组件

创建组件时可以使用继承的方式。举个例子,如果想要为两种不同的用户创建一个 UserCard,你可以先创建一个基本的 UserCard 然后将它拓展为两种特定的用户卡片。想要了解更多组件继承的知识,可以查看Google web developers’ article

Lifecycle Callbacks生命周期回调函数

我们创建了当元素加入 DOM 后自动触发的 connectedCallback 方法。我们同样有元素从 DOM 中移除时触发的 disconnectedCallback 方法。 attributesChangedCallback(attribute, oldval, newval)方法会在我们改变定制组件的属性时被触发。

组件元素是类的实例

既然组件元素是类的实例,就可以在这些类中定义公用方法。这些公用方法可以用来允许其它定制组件/脚本来和这些组件产生交互,而不是只能改变这些组件的属性。

定义私有方法

可以通过多种方式定义私有方法。我倾向于使用(立即执行函数),因为它们易写和易理解。举个例子,如果你创建的组件有非常复杂的内部功能,你可以像下面这样做:

(function() {

  // 使用第一个self参数来定义私有函数
  // 当调用这些函数时,从类中传递参数
  function _privateFunc(self, otherArgs) { ... }

  // 现在函数只可以在你的类的作用域中可用
  class MyComponent extends HTMLElement {
    ...

    // 定义下面这样的函数可以让你有途径和这个元素交互
    doSomething() {
      ...
      _privateFunc(this, args)
    }
    ...
  }

  customElements.define('my-component', MyComponent);
})()

冻结类

为了防止新的属性被添加,需要冻结你的类。这样可以防止类的已有属性被移除,或者已有属性的可枚举、可配置或可写属性被改变,同样也可以防止原型被修改。你可以使用下面的方法:

class MyComponent extends HTMLElement { ... }
const FrozenMyComponent = Object.freeze(MyComponent);
customElements.define('my-component', FrozenMyComponent);

注意: 冻结类会阻止你在运行时添加补丁并且会让你的代码难以调试。

结论

这篇关于 Web Component 的教程作用非常有限。这可以部分归咎于对 Web Component 的影响很大的 React。我希望这篇文章可以提供给你足够的信息来让你尝试不添加任何依赖来构建自己的定制组件。你可以在 定制组件 API 规范(Custom components API spec) 找到更多关于 Web Component 的信息。

你可以在这里阅读第二部分的教程:使用纯粹的JS构建 Web Component - Part 2!

目录
相关文章
|
22天前
|
JSON 缓存 JavaScript
深入浅出:使用Node.js构建RESTful API
在这个数字时代,API已成为软件开发的基石之一。本文旨在引导初学者通过Node.js和Express框架快速搭建一个功能完备的RESTful API。我们将从零开始,逐步深入,不仅涉及代码编写,还包括设计原则、最佳实践及调试技巧。无论你是初探后端开发,还是希望扩展你的技术栈,这篇文章都将是你的理想指南。
|
15天前
|
JSON JavaScript 前端开发
深入浅出Node.js:从零开始构建RESTful API
在数字化时代的浪潮中,后端开发作为连接用户与数据的桥梁,扮演着至关重要的角色。本文将引导您步入Node.js的奇妙世界,通过实践操作,掌握如何使用这一强大的JavaScript运行时环境构建高效、可扩展的RESTful API。我们将一同探索Express框架的使用,学习如何设计API端点,处理数据请求,并实现身份验证机制,最终部署我们的成果到云服务器上。无论您是初学者还是有一定基础的开发者,这篇文章都将为您打开一扇通往后端开发深层知识的大门。
30 12
|
17天前
|
监控 前端开发 JavaScript
使用 MERN 堆栈构建可扩展 Web 应用程序的最佳实践
使用 MERN 堆栈构建可扩展 Web 应用程序的最佳实践
27 6
|
19天前
|
存储 消息中间件 缓存
构建互联网高性能WEB系统经验总结
如何构建一个优秀的高性能、高可靠的应用系统对每一个开发者至关重要
23 2
|
19天前
|
JavaScript
使用Node.js创建一个简单的Web服务器
使用Node.js创建一个简单的Web服务器
|
23天前
|
机器学习/深度学习 人工智能 JavaScript
JavaScript和TypeScript的未来发展趋势及其在Web开发中的应用前景
本文探讨了JavaScript和TypeScript的未来发展趋势及其在Web开发中的应用前景。JavaScript将注重性能优化、跨平台开发、AI融合及WebAssembly整合;TypeScript则强调与框架整合、强类型检查、前端工程化及WebAssembly的深度结合。两者结合发展,特别是在Vue 3.0中完全采用TypeScript编写,预示着未来的Web开发将更加高效、可靠。
37 4
|
21天前
|
JavaScript NoSQL API
深入浅出Node.js:从零开始构建RESTful API
在数字化时代的浪潮中,后端开发如同一座灯塔,指引着数据的海洋。本文将带你航行在Node.js的海域,探索如何从一张白纸到完成一个功能完备的RESTful API。我们将一起学习如何搭建开发环境、设计API结构、处理数据请求与响应,以及实现数据库交互。准备好了吗?启航吧!
|
24天前
|
缓存 JavaScript 前端开发
JavaScript 与 DOM 交互的基础及进阶技巧,涵盖 DOM 获取、修改、创建、删除元素的方法,事件处理,性能优化及与其他前端技术的结合,助你构建动态交互的网页应用
本文深入讲解了 JavaScript 与 DOM 交互的基础及进阶技巧,涵盖 DOM 获取、修改、创建、删除元素的方法,事件处理,性能优化及与其他前端技术的结合,助你构建动态交互的网页应用。
38 5
|
23天前
|
开发框架 JavaScript 前端开发
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势。通过明确的类型定义,TypeScript 能够在编码阶段发现潜在错误,提高代码质量;支持组件的清晰定义与复用,增强代码的可维护性;与 React、Vue 等框架结合,提供更佳的开发体验;适用于大型项目,优化代码结构和性能。随着 Web 技术的发展,TypeScript 的应用前景广阔,将继续引领 Web 开发的新趋势。
35 2
|
23天前
|
缓存 负载均衡 JavaScript
构建高效后端服务:Node.js与Express框架实践
在数字化时代的浪潮中,后端服务的重要性不言而喻。本文将通过深入浅出的方式介绍如何利用Node.js及其强大的Express框架来搭建一个高效的后端服务。我们将从零开始,逐步深入,不仅涉及基础的代码编写,更会探讨如何优化性能和处理高并发场景。无论你是后端新手还是希望提高现有技能的开发者,这篇文章都将为你提供宝贵的知识和启示。