
customElements 是 Web Components 规范下的新 API,可以用来实现组件化开发。
如果你的应用只用兼容最新的 Chrome 浏览器那么用它来替代 React 或者 Vue 是一个不错的选择。
组件声明在一个 HTML 文件中。组件包括样式( Style ),节点( DOM )和交互逻辑( Script )。一个组件文件的基本结构如下:
<template> <style></style> <div>DOM 节点</div> </template> <script> const compOnentDocument= document.currentScript.ownerDocument; class Component extends HTMLElement { static get TAG_NAME() { return 'component-tag-name'; }; constructor() { super(); const shadow = this.attachShadow({ mode: 'closed' }); const cOntent= componentDocument.querySelector('template').content.cloneNode(true); shadow.appendChild(content); } } customElements.define(Component.TAG_NAME, Component); </script> 其中 template 节点下包含样式( Style )和节点( DOM )。交互逻辑在 script 标签中。
组件文件通过 <link rel="import" href="./component.html"> 的方式引入 HTML 文件中。在 HTML 文件中使用组件的方式就是直接写组件标签。比如:
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" cOntent="text/html; charset="> <meta name="viewport" cOntent="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"> <title>HTML</title> <link rel="import" href="./component.html"> </head> <body> <component-tag-name></component-tag-name> </body> </html> customElements.define API 用来组册组件,API 接受三个参数:组件标签名称、组件的类和组件继承的标签类型。如:
customElements.define('expanding-list', ExpandingList, { extends: "ul" }); 上面声明了一个标签为 expanding-list 的组件,组件的构造类是 ExpandingList 需要声明,组件继承 ul 标签的特性。
组件的构造类需要继承 HTMLElement 类,或者可以继承 HTMLParagraphElement 等 HTMLElement 的子类,如果继承了 HTMLParagraphElement 这个类,那么组件将拥有 p 标签的特性。
class Component extends HTMLElement { constructor() { super(); const shadow = this.attachShadow({ mode: 'closed' }); const cOntent= componentDocument.querySelector('template').content.cloneNode(true); shadow.appendChild(content); } } 组件内的构造函数是必须的,在构造函数内,我们先需要调用父类的构造函数,然后创建一个 Shadow DOM 节点,再将组件模版内容添加到节点内。
使用 Shadow DOM 可以做到组件内的样式和组件外的样式不互相干扰,可以让组件封装更彻底。
我们可以通过 document.currentScript.ownerDocument; 来拿到模版自身的跟节点。
组件可以像 HTML 标签一样使用属性。在组件中可以获取属性。
<component-tag-name attr-name="attr-value"></component-tag-name> class Component extends HTMLElement { constructor() { super(); const shadow = this.attachShadow({ mode: 'closed' }); const cOntent= componentDocument.querySelector('template').content.cloneNode(true); shadow.appendChild(content); const attrValue = this.getAttribute('attr-name'); } } connectedCallback 组件挂载,组件初始化后和移动时会触发disconnectedCallback 组件卸载adoptedCallback 组件被移动到一个新的文档树attributeChangedCallback 组件属性变化可以触发自定义事件。
<template> <style></style> <button>组件事件</button> </template> <script> const compOnentDocument= document.currentScript.ownerDocument; class Component extends HTMLElement { static get TAG_NAME() { return 'component-tag-name'; }; static get BUTTON_CLICK() { return 'button-click'; } constructor() { super(); const shadow = this.attachShadow({ mode: 'closed' }); const cOntent= componentDocument.querySelector('template').content.cloneNode(true); shadow.appendChild(content); const button = shadow.querySelector('button'); button.addEventListener('click', () => { this.dispatchEvent(new CustomEvent(Component.BUTTON_CLICK, { button })); }); } } customElements.define(Component.TAG_NAME, Component); </script> 1 love 2019 年 10 月 23 日 via Android 感觉不是正确方向,像 css in js 向 js 端靠拢才是未来 |
2 yyfearth 2019 年 10 月 23 日 @love Web Component 可以基于 JS 来做啊 然后用 css in js 那样把 template 和 css 包进来 好处是组件化标签 组件内部不透明 不会收到外部的干扰 |
3 yyfearth 2019 年 10 23 日 用 import 来导入组件 然后用 webpack 之类的打包在一起 |
4 SilentDepth 2019 年 10 月 23 日 想起 Svelte 的作者 Rich Harris 发过的一篇文章:Why I don't use web components ( https://dev.to/richharris/why-i-don-t-use-web-components-2cia) |
5 vivaxy OP @SilentDepth 实际使用中确实会遇到很多问题,尤其是 <link rel="import"> 的用法已经被标准废弃了 |