资讯专栏INFORMATION COLUMN

自定义元素探秘及构建可复用组件最佳实践

CoorChice / 507人阅读

摘要:若自定义元素标签名称不可用则摒弃。总之,自定义元素让开发者的代码更易理解和维护,并分割为小型,可复用及可封装的模块。被称为自定义元素接口,虽然现在仍然可用,但是已经被弃用并被认为是糟糕的实现。

原文请查阅这里,略有删减,本文采用知识共享署名 4.0 国际许可协议共享,BY Troland。

这是 JavaScript 工作原理第十九章。

概述

在 前述文章中,我们介绍了 Shadow DOM 接口和一些其它概念,而这些都是网页组件的组成部分。网页组件背后的思想即通过创建颗粒化,模块化和可复用的元素来扩展 HTML 内置功能。这是一个已经被所有主流浏览器兼容的相对崭新的 W3C 标准且可以被用在生产环境之中,虽然不兼容的浏览器需要使用垫片库(将在随后的章节中进行讨论)。

正如开发者所知,浏览器为构建网站和网页程序提供了一些重要的开发工具。我们所说的 HTML,CSS 和 JavaScript 即开发者使用 HTML 来构建结构,CSS 进行样式化然后使用 JavaScript 来让页面动起来。然而,在网页组件出现之前,把 JavaScript 脚本和 HTML 结构组合起来并非易事。

本文将阐述网页组件的基石-自定义元素。总之,开发者可以使用自定义元素接口来创建包含 JavaScript 逻辑和样式的自定义元素(正如名称的字面意思)。许多开发者会把自定义元素和 shadow DOM 混为一谈。但是,他们是完全不同的概念且它们互补而不是可以相互替代的。

一些框架(比如 Angular,React) 试图通过引进其自有概念来解决同样的问题。开发者可以把自定义元素和 Angular 的指令或者 React 组件进行对比。然而,自定义元素是浏览器原生的且只需要原生 JavaScript,HTML 和 CSS。当然了,这并不意味着它可以取代一个典型的 JavaScript 框架。现代框架不仅仅为开发者提供模仿自定义元素行为的能力。因此,可以同时使用框架和自定义元素。

接口

在深入了解之前,让我们先大概快速浏览一下接口的内容。全局 customElements 对象为开发者提供了一些方法:

define(tagName, constructor, options) -创建一个新的自定义元素。

包含三个参数:自定义元素的可用标签名称,自定义元素类定义及选项参数对象。目前仅支持一个选项参数:extends 指定想要扩展的 HTML 内置元素名称的字符串。用来创建定制化内置元素。

get(tagName) -若元素已经定义则返回自定义元素的构造函数否则返回 undefined。只有一个参数:自定义元素的可用标签名称。

whenDefined(tagName)-返回一个 promise 对象,当定义自定义元素即解析。若元素已定义则立即进行解析。若自定义元素标签名称不可用则摒弃 promise。只有一个参数:自定义元素的可用标签名称。

如何创建自定义元素

创建自定义元素实际上就是小菜一碟。开发者只需要做两件事:创建扩展 HTMLElement 类元素的类定义,然后以合适的名称注册元素。

class MyCustomElement extends HTMLElement {
  constructor() {
    super();
    // …
  }

  // …
}

customElements.define("my-custom-element", MyCustomElement);

或者如你所愿,可以使用匿名类以防止弄乱当前作用域

customElements.define("my-custom-element", class extends HTMLElement {
  constructor() {
    super();
    // …
  }

  // …
});

从以上例子可见,使用 customElements.define(...) 方法注册自定义元素。

自定义元素所解决的问题

实际上,问题是啥?嵌套 DIV 是问题之一。嵌套 Div 是啥?在现代网页程序中这是一个非常常见的现象,开发者会使用多个嵌套块状元素(div 互相嵌套之类)。

因为浏览器可以在页面上正常进行渲染,所以使用了这样的嵌套结构。但是,这会使得 HTML 不具可读性且难以维护。

因此,例如假设有如下组件:

那么传统 HTML 结构类似如下:

 
 
 
 

但想象下如果可以使用类似如下代码:


  
    
    
    
    
  

要我说,第二个示例清爽多了。第二个示例更具可维护性,可读性且对于浏览器和开发者更加合理。更加简洁。

另一个问题即可复用性。作为开发者,不仅仅要书写可运行的代码还得写出可维护代码。书写可维护代码即能够轻易地复用代码片段而不是重复地复制粘贴。

我将会给出一个简单的示例而你就会明白。假设有如下元素:

若需要在其它地方使用这段代码,开发者需要再次书写相同的 HTML 结构。现在,想象 一下需要稍微修改一下这些元素。开发者需要找出每个代码需要修改的地方,然后一遍遍地做出同样的修改。太恶心了。。。

若使用如下码岂不会更好?

现代网页程序不仅仅只有静态 HTML。开发者需要做交互。这就需要 JavaScript。一般来说,开发者需要做的即创建一些元素然后在上面监听事件以响应用户输入。点击,拖拽或者悬浮事件等等。

var myDiv = document.querySelector(".my-custom-element");

myDiv.addEventListener("click", () => {
  myDiv.innerHTML = " I have been clicked ";
});
I have not been clicked yet.

使用自定义元素接口可以把所有的逻辑封装进元素自身。以下代码可以实现和上面代码一样的功能:

class MyCustomElement extends HTMLElement {
  constructor() {
    super();

    var self = this;

    self.addEventListener("click", () => {
      self.innerHTML = " I have been clicked ";
    });
  }
}

customElements.define("my-custom-element", MyCustomElement);

  I have not been clicked yet

咋一看上去,自定义元素技术需要书写更多的 JavaScript 代码。但是在实际程序中,创建不需复用的单一组件的情况是很少见的。一个典型的现代网页程序的重要特征即大多数元素都是动态创建的。那么,开发者就需要分别处理使用 JavaScript 动态添加元素或者使用 HTML 结构中预定义内容。那么可以使用自定义元素来实现这些功能。

总之,自定义元素让开发者的代码更易理解和维护,并分割为小型,可复用及可封装的模块。

要求

在创建自定义元素之前,开发者需要遵守如下特殊规则:

名称必须包含一个破折号 - 。这样 HTML 解析器就可以把自定义元素和内置元素区分开来。这样可以保证不会和内置元素出现命名冲突的问题(不管是现在或者将来当添加其它元素的时候)。比如, 是正确的而 myCustomElement 则不然。

不允许重复注册标签名称。重复注册标签名称会导致浏览器抛出 DOMException 错误。不可以覆盖已注册自定义元素。

自定义元素不可以自关闭。HTML 解析器只允许一小撮内置元素可以自关闭(比如

let myCustomElementTemplate = document.querySelector("#my-custom-element-template");

class MyCustomElement extends HTMLElement {
  // ...

  constructor() {
    super();

    let shadowRoot = this.attachShadow({mode: "open"});
    shadowRoot.appendChild(myCustomElementTemplate.content.cloneNode(true));
  }

  // ...
});

那么现在,我们在自定义元素里面使用了 shadow DOM 和 模板,创建了一个元素,该元素作用域和其它元素隔绝且把 HTML 结构和 JavaScript 逻辑完美地隔离开来。

样式化

那么,我们讲解了 HTML 和 JavaScript,现在还剩下 CSS。显然,需要样式化元素。开发者可以在 shadow DOM 中添加样式但是用户如何从外部样式化元素呢?答案很简单-只需要和一般的内置元素一样写样式即可。

my-custom-element {
  border-radius: 5px;
  width: 30%;
  height: 50%;
  // ...
}

请注意外部定义的样式比元素内部定义的样式优先级高,外部样式会覆盖掉元素内定义的样式。

开发者需要明白有时候页面渲染,然后会在某些时刻会发现无样式内容闪烁(FOUC)。开发者可以通过为未定义组件定义样式及当元素已定义的时候使用一些动画过渡效果。使用 :defined 选择器来达成这一效果。

my-button:not(:defined) {
  height: 20px;
  width: 50px;
  opacity: 0;
}
未知元素对比未定义自定义元素

HTML 规范非常灵活且允许开发者任意声明标签。若不被浏览器解析则会解析为 HTMLUnknownElement

var element = document.createElement("thisElementIsUnknown");

if (element instanceof HTMLUnknownElement) {
  console.log("The selected element is unknown");
}

但是这并不适用于自定义元素。还记得讨论定义自定义元素时候的特殊命名规则吗?原因是因为当浏览器发现一个自定义元素的名称有效的时候,浏览器会把它解析为 HTMLElement ,然后浏览器会把它看作一个未定义的自定义元素。

var element = document.createElement("this-element-is-undefined");

if (element instanceof HTMLElement) {
  console.log("The selected element is undefined but not unknown");
}

在视觉上, HTMLElement 和 HTMLUnknownElement 可能没啥不同,但是需要注意其它地方。解析器会区别对待这两种元素。具有有效自定义名称的元素会被看作拥有自定义元素实现。在定义实现细节之前该自定义元素会被看成一个空 div 元素。而一个未定义元素没有实现任何内置元素的任何方法或属性。

浏览器兼容

custom elements 第一版是在 Chrome 36+ 中引入的。被称为自定义元素接口 v0,虽然现在仍然可用,但是已经被弃用并被认为是糟糕的实现。若想要学习 v0 版,可以阅读这篇文章。从 Chrome 54 和 Safari 10.1(虽然只有部分支持) 开始支持自定义元素接口 v1,微软 Edge 还处于其原型设计阶段而 Mozilla 从 v50 开始支持,但默认不支持需要显式启用。目前只有 webkit 浏览器完全支持。然而,如上所述,可以使用垫片库兼容到包括 IE11 在内的所有浏览器。

检测可用性

通过检查 window 对象中的 customElements 属性是否可用来检查浏览器是否支持自定义元素。

const supportsCustomElements = "customElements" in window;

if (supportsCustomElements) {
  // 可以使用自定义元素接口
}

否则需要使用垫片库:

function loadScript(src) {
  return new Promise(function(resolve, reject) {
    const script = document.createElement("script");

    script.src = src;
    script.onload = resolve;
    script.onerror = reject;

    document.head.appendChild(script);
  });
}

// Lazy load the polyfill if necessary.
if (supportsCustomElements) {
  // 浏览器原生支持自定义元素
} else {
  loadScript("path/to/custom-elements.min.js").then(_ => {
    // 加载自定义元素垫片
  });
}

总之,网页组件标准中的自定义元素为开发者提供了如下功能:

把 JavaScript 和 CSS 样式整合入 HTML 元素

允许开发者扩展已有的 HTML 元素(内置和其它自定义元素)

不需要其它库或者框架的支持。只需要原生 JavaScript,HTML 和 CSS 还有可选的垫片库来支持旧浏览器。

可以和其它网页组件功能无缝衔接(shadow DOM,模板,插槽等)。

和浏览器开发者工具紧密集成在一起。

使用已知的可访问功能

总之,自定义元素和开发者已经使用过的组件技术并没有什么大的不同。它只让开发网页程序过程更加便携的另一种方式。那么,它让更快地构建非常复杂的程序成为可能。

参考资料:

https://developers.google.com...

https://www.html5rocks.com/en...

https://github.com/w3c/webcom...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/102256.html

相关文章

  • 前端每周清单半年盘点之 React 与 ReactNative 篇

    摘要:前端每周清单半年盘点之与篇前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点分为新闻热点开发教程工程实践深度阅读开源项目巅峰人生等栏目。与求同存异近日,宣布将的构建工具由迁移到,引发了很多开发者的讨论。 前端每周清单半年盘点之 React 与 ReactNative 篇 前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点;分为...

    Barry_Ng 评论0 收藏0
  • 【译】 eBay 的速度与风范

    摘要:本文转载自众成翻译译者文蔺链接原文今年的顶级举措之一是为我们的用户提供一个更好的浏览体验。这意味着保持最少的。这些组件有全局的,网站速度信标现场速度信标套件,试验的库文件,以及统计模块等。它们在发布前要经历严格的回归测试,这就会增加延时。 本文转载自:众成翻译译者:文蔺链接:http://www.zcfy.cc/article/912原文:http://www.ebaytechblog...

    davidac 评论0 收藏0
  • 关于Vue2一些值得推荐的文章 -- 五、六月份

    摘要:五六月份推荐集合查看最新的请点击集前端最近很火的框架资源定时更新,欢迎一下。苏幕遮燎沈香宋周邦彦燎沈香,消溽暑。鸟雀呼晴,侵晓窥檐语。叶上初阳乾宿雨,水面清圆,一一风荷举。家住吴门,久作长安旅。五月渔郎相忆否。小楫轻舟,梦入芙蓉浦。 五、六月份推荐集合 查看github最新的Vue weekly;请::点击::集web前端最近很火的vue2框架资源;定时更新,欢迎 Star 一下。 苏...

    sutaking 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<