js基础:WebComponents 和 ShadowDOM

WebComponents 和 ShadowDOM

组件化存在的问题

HTML 和 CSS 是如何阻碍前端组件化的,一个简单的例子:

1
2
3
4
5
6
7
<style>
p {
background-color: brown;
color: cornsilk;
}
</style>
<p>I am river</p>

问题:

  1. CSS 是影响全局的
  2. 任何地方都可以直接读取和修改 DOM

WebComponent

WebComponent 给出了解决思路,它提供了对局部视图封装能力,可以让 DOM、CSSOM 和 JavaScript 运行在局部环境中,这样就使得局部的 CSS 和 DOM 不会影响到全局。
WebComponent 是一套技术的组合,具体涉及到了 Custom elements(自定义元素)、Shadow DOM(影子 DOM)和 HTML templates(HTML 模板)

影子 DOM

影子 DOM 的作用是将模板中的内容与全局 DOM 和 CSS 进行隔离,这样我们就可以实现元素和样式的私有化了。你可以把影子 DOM 看成是一个作用域,其内部的样式和元素是不会影响到全局的样式和元素的,而在全局环境下,要访问影子 DOM 内部的样式或者元素也是需要通过约定好的接口的。

实现例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<!DOCTYPE html>
<html>
<body>
<!--
一:定义模板
二:定义内部CSS样式
三:定义JavaScript行为
-->
<template id="river-t">
<style>
p {
background-color: brown;
color: cornsilk;
}

div {
width: 200px;
background-color: bisque;
border: 3px solid chocolate;
border-radius: 10px;
}
</style>
<div>
<p>I am river</p>
<p>I am river</p>
</div>
<script>
function foo() {
console.log('inner log')
}
</script>
</template>
<script>
class River extends HTMLElement {
constructor() {
super()
//获取组件模板
const content = document.querySelector('#river-t').content
//创建影子DOM节点
const shadowDOM = this.attachShadow({ mode: 'open' })
//将模板添加到影子DOM上
shadowDOM.appendChild(content.cloneNode(true))
}
}
customElements.define('river', River)
</script>

<river></river>
<div>
<p>I am river</p>
<p>I am river</p>
</div>
<river></river>
</body>
</html>
  1. 首先,使用 template 属性来创建模板。利用 DOM 可以查找到模板的内容,但是模板元素是不会被渲染到页面上的,也就是说 DOM 树中的 template 节点不会出现在布局树中,所以我们可以使用 template 来自定义一些基础的元素结构,这些基础的元素结构是可以被重复使用的。

  2. 其次,我们需要创建一个 River 的类。在该类的构造函数中要完成三件事:查找模板内容;创建影子 DOM;再将模板添加到影子 DOM 上。

  3. 最后,就很简单了,可以像正常使用 HTML 元素一样使用该元素,如上述代码中的。

以上

在没有 webcomponent 的时候,通过 react 和 vue 基于当前的前端特性去实现组件化,他们之间是互相影响和借鉴的,最终 react 和 vue 也会向 webcomponent 标准的方向演进。但是现在由于 webcomponent 的浏览器支持还不是太好,所以现阶段它们还是会并存的。Vue,React 是从开发者层面解决了组件化的问题,提高了效率。WebComponent 是从浏览器引擎实现层面解决了组件化的问题。