# 什么是WebComponent?
WebComponent 汉语直译过来第一感觉是web组件的意思,但是它只是一套规则、一套API。你可以通过这些API创建自定义的新的组件,并且组件是可以重复使用的,封装好的组件可以在网页和Web应用程序中进行使用。
当前的前端开发环境,Vue、React等都基于组件化开发的形式,但是他们的组件生态并不互通,如果你有过两个平台的开发经验的话,你应该知道最烦恼的就是两个平台的UI组件表现不一致的问题。
我们抽离组件为了提高复用率,提升开发效率。但是脱离了像Vue、React这样的框架后,你会发现,原生JS难道就不能开发自定义组件吗?WebComponent就是为了解决这个问题。
换一个角度来说,并不是所有的业务场景都需要Vue\React这样的框架进行开发、也并是都需要工程化。很多业务场景我们需要原生JS、HTML。
言归正传,WebComponent实现的组件可以和HTML原生标签一起使用,有了这个概念之后,我们看一下它的具体表现形式是怎样的。

上面我们看到<body>标签还是我们熟悉的标签,但是<custom-component>标签就是自定义组件的标签了,它不属于html语义化标签中的任何一个,是自定义的,还是蛮酷的。
接下来我们从这个简单的DEMO入手,对WebComponent进行了解。首先就是三大规范:
- Custom Elements规范
- Template规范
- Shadow DOM规范
# Custom Element
所谓自定义元素,即当内置元素无法为问题提供解决方案时,自己动手来创建一个自定义标记来解决,上方的<custom-component>就是我们手动创建的自定义标记。
这里有个小知识点,为什么要把标签叫做标记呢?前面的文字描述中我一直说的都是标签。还记得HTML的全称吗:HTML(HyperText Markup Language)超文本标记语言。对吧,标记语言。讲解知识点的时候要严谨一点,哈哈。
元素的状态是指定义该元素(或者叫做升级该元素)时元素状态的改变,升级过程是异步的。 元素内部的状态有:
undefined未升级:即自定义元素还未被define。failed升级失败:即define过了也实例化开了,但失败了。会自动按HTMLUnknownElement类来实例化。uncustomized未定制化:没有define过但却被实例化了,会自动按HTMLUnknownElement类来实例化。custom升级成功:define过并且实例化成功了。
我们先从代码结构上来看。

首先可以看出,首先有个类的概念。自定义元素类必须继承自window内置的HTML相关类, 这些类位于 window.<HTML*Element>,他们都继承自HTMLElement类。
然后在constructor中定义类一些标记模版,定义模板后,执行this.appendChild,其中this指向了当前类实例。
最后将自定义组件挂载到customElements上,通过window.customElements.define方法。这个时候注意了,需要给自定义组件起一个名字,可以看到上面例子中我起的名字为custom-component。起名字是有规则的,规则如下:
- 自定义元素的名称,必须包含短横线(-)。它可以确保html解析器能够区分常规元素和自定义元素,还能确保html标记的兼容性。
- 自定义元素只能一次定义一个,一旦定义无法撤回。
- 自定义元素不能单标记封闭。比如
<custom-component />,必须写一对开闭标记。比如<custom-component></custom-component>。
对于自定义组件挂载的相关API:
window.customElement.define('custom-component', CustomComponent, extendsInit)// 定义一个自定义元素window.customElement.get('custom-component')// 返回已定义的自定义元素的构造函数window.customElement.whenDefined('custom-component')// 接收一个promise对象,是当定义自定义元素时返回的,可监听元素状态变化但无法捕捉内部状态值。
其中window.customElement.whenDefined方法监听的元素状态为上述讲解的四种元素状态中的: failed升级失败和custom升级成功。
讲到这里我们停一下,我们执行一下上述自定义的组件看一下效果。
这里我写了一些style样式为了更美观的体现自定义标签。样式如下:

效果图:

可以看到右侧HTML标记中渲染了我们上面自定义的标记<custom-component>并且配合style样式,渲染了出来。效果不错是吧。
这里有个问题,我们DEMO里的DOM结构比较简单,所以我们通过document.createElement、appendChild方法进行构建还不算复杂,如果DOM结构很复杂的组件怎么办呢?一顿使用createElement也不是个办法。这个时候我们就要引入<template>标记了。
# Template
Web Components API 提供了<template>标签,可以在它里面使用HTML定义DOM结构。这样的话我们改版一下我们的自定义组件:

这里有两个需要考虑的地方:
- 这里因为是DEMO演示所以把
<template>标签写在了一起,其实可以用脚本把<template>注入网页。这样的话,JavaScript 脚本跟<template>就能封装成一个 JS 文件,成为独立的组件文件。网页只要加载这个脚本,就能使用<custom-component>组件。 <template>标签内的节点进行操作必须通过templateElem.content返回的节点来操作。因为这里获取的templateElem并不是一个正常的DOM结构,在控制台打印一下templateElem.content得到的结果是#document-fragment。它其实是DocumentFragment节点,里面才是真正的结构。而且这个模板还要留给其他实例使用,所以不能直接移动它的子元素
这两个地方理解了之后,我们全局审视一下,我们既然是封装组件,那么自定义标记的样式放在外面是不是有些不太合适呢?我自定义了组件样式还要放在外面的话,就失去了一部分组件化开发的概念,而且可能造成样式污染问题。我们平常开发React等组件的时候,样式也是要和组件一起封装起来的。
WebComponent也是可以把样式写在组件中的,我们看一下WebComponent中的style的语法,修改一下上方D
