组件是前端的发展方向,现在流行的 React 和 Vue 都是组件框架。
谷歌公司由于掌握了 Chrome 浏览器,一直在推动浏览器的原生组件,即 Web Components API。相比第三方框架,原生组件简单直接,符合直觉,不用加载任何外部模块,代码量小。目前,它还在不断发展,但已经可用于生产环境。
Web Components API 内容很多,本文不是全面的教程,只是一个简单演示,让大家看一下怎么用它开发组件。
下图是一个用户卡片。
本文演示如何把这个卡片,写成 Web Components 组件,这里是最后的 完整代码。
网页只要插入下面的代码,就会显示用户卡片。
这种自定义的 HTML 标签,称为自定义元素(custom element)。根据规范,自定义元素的名称必须包含连词线,用与区别原生的 HTML 元素。所以,
不能写成
。
customElements.define()
自定义元素需要使用 JavaScript 定义一个类,所有
都会是这个类的实例。
class UserCard extends HTMLElement { constructor() { super(); } }
上面代码中, UserCard
就是自定义元素的类。注意,这个类的父类是 HTMLElement
,因此继承了 HTML 元素的特性。
接着,使用浏览器原生的 customElements.define()
方法,告诉浏览器
元素与这个类关联。
window.customElements.define('user-card', UserCard);
自定义元素
目前还是空的,下面在类里面给出这个元素的内容。
class UserCard extends HTMLElement { constructor() { super(); var image = document.createElement('img'); image.src = 'https://semantic-ui.com/images/avatar2/large/kristy.png'; image.classList.add('image'); var container = document.createElement('div'); container.classList.add('container'); var name = document.createElement('p'); name.classList.add('name'); name.innerText = 'User Name'; var email = document.createElement('p'); email.classList.add('email'); email.innerText = '[email protected]'; var button = document.createElement('button'); button.classList.add('button'); button.innerText = 'Follow'; container.append(name, email, button); this.append(image, container); } }
上面代码最后一行, this.append()
的 this
表示自定义元素实例。
完成这一步以后,自定义元素内部的 DOM 结构就已经生成了。
标签使用 JavaScript 写上一节的 DOM 结构很麻烦,Web Components API 提供了 标签,可以在它里面使用 HTML 定义 DOM。
User Name
[email protected]
然后,改写一下自定义元素的类,为自定义元素加载 。
class UserCard extends HTMLElement { constructor() { super(); var templateElem = document.getElementById('userCardTemplate'); var content = templateElem.content.cloneNode(true); this.appendChild(content); } }
上面代码中,获取 节点以后,克隆了它的所有子元素,这是因为可能有多个自定义元素的实例,这个模板还要留给其他实例使用,所以不能直接移动它的子元素。
到这一步为止,完整的代码如下。
...
自定义元素还没有样式,可以给它指定全局样式,比如下面这样。
user-card { /* ... */ }
但是,组件的样式应该与代码封装在一起,只对自定义元素生效,不影响外部的全局样式。所以,可以把样式写在 里面。
User Name
[email protected]
上面代码中, 样式里面的
:host
伪类,指代自定义元素本身。
内容现在是在 里面设定的,为了方便使用,把它改成参数。
代码也相应改造。
最后,改一下类的代码,把参数加到自定义元素里面。
class UserCard extends HTMLElement { constructor() { super(); var templateElem = document.getElementById('userCardTemplate'); var content = templateElem.content.cloneNode(true); content.querySelector('img').setAttribute('src', this.getAttribute('image')); content.querySelector('.container>.name').innerText = this.getAttribute('name'); content.querySelector('.container>.email').innerText = this.getAttribute('email'); this.appendChild(content); } } window.customElements.define('user-card', UserCard);
我们不希望用户能够看到
的内部代码,Web Component 允许内部代码隐藏起来,这叫做 Shadow DOM,即这部分 DOM 默认是隐藏的,开发者工具里面看不到。
自定义元素的 this.attachShadow()
方法开启 Shadow DOM,详见下面的代码。
class UserCard extends HTMLElement { constructor() { super(); var shadow = this.attachShadow( { mode: 'closed' } ); var templateElem = document.getElementById('userCardTemplate'); var content = templateElem.content.cloneNode(true); content.querySelector('img').setAttribute('src', this.getAttribute('image')); content.querySelector('.container>.name').innerText = this.getAttribute('name'); content.querySelector('.container>.email').innerText = this.getAttribute('email'); shadow.appendChild(content); } } window.customElements.define('user-card', UserCard);
上面代码中, this.attachShadow()
方法的参数 { mode: 'closed' }
,表示 Shadow DOM 是封闭的,不允许外部访问。
至此,这个 Web Component 组件就完成了,完整代码可以访问 这里。可以看到,整个过程还是很简单的,不像第三方框架那样有复杂的 API。
在前面的基础上,可以对组件进行扩展。
(1)与用户互动
用户卡片是一个静态组件,如果要与用户互动,也很简单,就是在类里面监听各种事件。
this.$button = shadow.querySelector('button'); this.$button.addEventListener('click', () => { // do something });
(2)组件的封装
上面的例子中, 与网页代码放在一起,其实可以用脚本把
注入网页。这样的话,JavaScript 脚本跟
就能封装成一个 JS 文件,成为独立的组件文件。网页只要加载这个脚本,就能使用
组件。
这里就不展开了,更多 Web Components 的高级用法,可以接着学习下面两篇文章。
- Web Components Tutorial for Beginners
- Custom Elements v1: Reusable Web Components
(完)