这是我参与8月更文挑战的第10天,活动详情查看:8月更文挑战 在使用Web Components之前,我们先看看上一篇文章Web Components简介,其中提到了相关的接口、属性和方法。 正是这些接口、属性和方法才实现了Web Components的主要技术:Custom elements(自定义元素)、Shadow DOM(影子DOM)、HTML templates(HTML模板)。 由于并不是所有的接口以及接口所包含的方法都会被用到,所以我们从实际的案例出发,逐步了解Web Components的使用。
需求1:创建一个基础的组件,包含一个输入框,和一个button。
mian.js
class SearchInput extends HTMLElement {
constructor() {
super();
// 创建一个 shadow root
let shadow = this.attachShadow({mode: 'open'});
const input = document.createElement('input');
input.setAttribute('type', 'text');
input.setAttribute('class', 'input-vlaue');
const button = document.createElement('input');
button.setAttribute('type', 'button');
button.setAttribute('value', 'Search');
// 创建一些 CSS,并应用到 shadow dom上
let style = document.createElement('style');
style.textContent=".input-vlaue{margin:5px; color:red;}";
shadow.append(input);
shadow.append(button);
shadow.append(style);
}
}
// declare var customElements: CustomElementRegistry;
customElements.define('search-input', SearchInput);
复制代码
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./main.js"></script>
</head>
<body>
<search-input></search-input>
<search-input></search-input>
<search-input></search-input>
</body>
</html>
复制代码
这样子,一个input + button的组件就实现了。这里用到的技术有Custom elements(自定义元素)、Shadow DOM(影子DOM)。
使用Shadow DOM的好处:Shadow DOM 内部的元素始终不会影响到它外部的元素
**要注意的是,不是每一种类型的元素都可以附加到shadow root(影子根)下面。**出于安全考虑,一些元素不能使用 shadow DOM(例如<a>
),以及许多其他的元素。
Element.attachShadow() 方法给指定的元素挂载一个Shadow DOM,并且返回对 ShadowRoot 的引用。具体方法:创建一个ShadowRoot并返回它:
attachShadow(init: ShadowRootInit): ShadowRoot;
复制代码
attachShadow()的参数是一个对象,里面包含两个属性,mode和delegatesFocus。
mode:可以是open/closed。
- open:shadow root元素可以从js外部访问根节点
- closed:拒绝从js外部访问关闭的shadow root节点
delegatesFocus 焦点委托
一个布尔值, 当设置为 true 时, 指定减轻自定义元素的聚焦性能问题行为. 当shadow DOM中不可聚焦的部分被点击时, 让第一个可聚焦的部分成为焦点, 并且shadow host(影子主机)将提供所有可用的 :focus 样式.
使用Custom elements(自定义元素)的好处:语义化,简单明了。
customElements.define('search-input', SearchInput)实现了CustomElementRegistry接口,无返回值:
interface CustomElementRegistry {
define(name: string, constructor: CustomElementConstructor, options?: ElementDefinitionOptions): void;
get(name: string): any;
upgrade(root: Node): void;
whenDefined(name: string): Promise<void>;
}
复制代码
需求2:可是真正的组件不仅仅有显示的功能,还需要绑定一些事件,例如上面的例子,点击了如何触发search事件呢?
核心:element.addEventListener()
代码示例(index.html不变):
class SearchInput extends HTMLElement {
constructor() {
super();
// 创建一个 shadow root
let shadow = this.attachShadow({mode: 'open'});
const input = document.createElement('input');
input.setAttribute('type', 'text');
input.setAttribute('class', 'input-vlaue');
const button = document.createElement('input');
button.setAttribute('type', 'button');
button.setAttribute('value', 'Search');
const text = document.createElement('p');
// 创建一些 CSS,并应用到 shadow dom上
let style = document.createElement('style');
style.textContent=".input-vlaue{margin:5px; color:red;}";
shadow.append(input);
shadow.append(button);
shadow.append(text);
shadow.append(style);
button.addEventListener('click', e => {
text.textContent = '按钮被点击了~'
});
}
}
// declare var customElements: CustomElementRegistry;
customElements.define('search-input', SearchInput);
复制代码
需求3:我们知道,像react、vue都有组件自身的状态管理,和利用Props进行数据传递,那么,在web components中是怎么实现的呢?
核心:this.getAttribute(props),class内部属性,生命周期
main.js
class SearchInput extends HTMLElement {
constructor() {
super();
this.state = { count:0 };
// 创建一个 shadow root
let shadow = this.attachShadow({mode: 'open'});
const input = document.createElement('input');
input.setAttribute('type', 'text');
input.setAttribute('class', 'input-value');
const button = document.createElement('input');
button.setAttribute('type', 'button');
button.setAttribute('value', 'Search');
const text = document.createElement('p');
// 创建一些 CSS,并应用到 shadow dom上
let style = document.createElement('style');
style.textContent=".input-vlaue{margin:5px; color:red;}";
shadow.append(input);
shadow.append(button);
shadow.append(text);
shadow.append(style);
button.addEventListener('click', e => {
this.state.count++;
text.textContent = `按钮被点击了${this.state.count}次。`
});
}
connectedCallback () {
const defaultValue = this.getAttribute('defaultValue');
const input = this.shadowRoot.querySelector('.input-value');
input.value = defaultValue;
}
}
// declare var customElements: CustomElementRegistry;
customElements.define('search-input', SearchInput);
复制代码
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./main.js"></script>
</head>
<body>
<search-input defaultValue="input1"></search-input>
<search-input defaultValue="input2"></search-input>
<search-input defaultValue="input3"></search-input>
</body>
</html>
复制代码
到此,我们已经了解了利用Web Components创建一个组件,如何触发组件的事件,如何利用props向组件内部传递数据以及组件内部的状态管理。
目前来看缺乏的就是组件间的通信了,目前还没发现有类似react、vue的组件间通信的方法,不过我们可以利用localStorage,StorageEvent间接的发生组件间的通信、界面渲染。
在上一章节中主要介绍了Custom elements(自定义元素),Shadow DOM(影子DOM)的使用,我们发现其实只使用上述的两种方式,已经可以构建我们平时需要的组件,那么为什么还需要HTML templates(HTML模板)技术呢? 不知道大家还记不记得之前创建元素的方式:
const input = document.createElement('input');
input.setAttribute('type', 'text');
input.setAttribute('class', 'input-vlaue');
复制代码
创建一个input就需要写这些js代码,很明显,如果当html文档结构太深或者节点过多时,写出太多的创建html 节点的代码,还是很头疼的。一方面代码量过多且层级结构不清晰,另一方面时css也是用此方式创建,如何使用伪类呢? 多少是方便的。所以便有了HTML templates(HTML模板)技术。
我们先来看下它的定义:
HTML内容模板(<template>
)元素是一种用于保存客户端内容机制,该内容在加载页面时不会呈现,但随后可以(原文为 may be)在运行时使用JavaScript实例化。") 和 <slot>
元素使您可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。
其实就是它可以让你在编写组件时,可以预先定义好模板,然后再在模板上发展出自己的组件。 示例: index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./main.js"></script>
</head>
<body>
<template id="my-paragraph">
<style>
p {
color: white;
background-color: #909090;
padding: 5px;
border: 1px soli silver;
}
</style>
<p><slot name="my-text">默认文本</slot></p>
</template>
<my-paragraph>
<span slot="my-text">个性化文本</span>
</my-paragraph>
<my-paragraph>
<ul slot="my-text">
<li>列表条目1</li>
<li>列表条目2</li>
</ul>
</my-paragraph>
</body>
</html>
复制代码
main.js
customElements.define('my-paragraph',
class extends HTMLElement {
constructor() {
super();
const template = document.getElementById('my-paragraph');
const templateContent = template.content;
this.attachShadow({mode: 'open'}).appendChild(
templateContent.cloneNode(true)
);
}
}
);
复制代码
定义模板的的代码:
<template id="my-paragraph">
<style>
p {
color: white;
background-color: #909090;
padding: 5px;
border: 1px soli silver;
}
</style>
<p><slot name="my-text">默认文本</slot></p>
</template>
复制代码
由于这里的html import已经不被各大浏览器支持了,所以这里的定义模板的模块只能写到使用的html文本里面,或者使用js创建,写入到main.js里面。如下的例子,将template独立到js文件中。 示例: index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./main.js"></script>
</head>
<body>
<my-paragraph>
<span slot="my-text">个性化文本</span>
</my-paragraph>
<my-paragraph>
<ul slot="my-text">
<li>列表条目1</li>
<li>列表条目2</li>
</ul>
</my-paragraph>
</body>
</html>
复制代码
main.js
const str = `
<style>
p {
color: white;
background-color: #909090;
padding: 5px;
border: 1px soli silver;
}
</style>
<p><slot name="my-text">默认文本</slot></p>
`;
customElements.define('my-paragraph',
class extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({mode: 'open'});
//const template = document.getElementById('my-paragraph');
// const templateContent = template.content;
const template = document.createElement('template');
template.innerHTML = str;
const templateContent = template.content;
shadow.appendChild(
templateContent.cloneNode(true)
);
}
}
);
复制代码
以上便是对Web Components的一个基本的认识和使用。由此可以看出Web Components在组件化开发方向的优势,可是在使用过程中也有一定劣势,以下便是目前自己不太喜欢的地方:
个人不太喜欢的地方
- 无法在组件内部使用公共的样式,例如引入了外部样式,即使在组件内部使用该样式对应的class name也不会起作用,例如下面的代码,虽然在 slot外部的p标签内加了class name,但是在组件外部也是无法使用.test{}来定制样式了,这样一来就无法使用很多开源的css类库了。
const str = `
<style>
p {
color: white;
background-color: #909090;
padding: 5px;
border: 1px soli silver;
}
</style>
<p class="test"><slot name="my-text">默认文本</slot></p>
`;
复制代码
- 没了html import 语法,template就只能定义显示的html文件中,显然这不是我们想要的组件,我们希望的组件是简洁明了,使用简单,而这看起来组件和html还有了耦合。当然也可以把template写入到js中,但是如果template结构复杂,就会发现自负串过长,代码结构不美观,且html,css,js混合到了一起。
- 其他的,暂时想不起来了...
参考网址:
MSDN:developer.mozilla.org/zh-CN/docs/…
web componets 实例代码:github.com/mdn/web-com…
HTML5 Rocks: www.html5rocks.com/en/tutorial…