首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  HTML

基于 Custom Elements 的组件化开发

  •  1
     
  •   vivaxy · 47 天前 · 731 次点击
    这是一个创建于 47 天前的主题,其中的信息可能已经有所发展或是发生改变。

    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=UTF-8">
      <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 类,或者可以继承 HTMLParagraphElementHTMLElement 的子类,如果继承了 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>
    

    例子

    5 回复  |  直到 2019-10-23 22:07:04 +08:00
        1
    love   46 天前 via Android
    感觉不是正确方向,像 css in js 向 js 端靠拢才是未来
        2
    yyfearth   46 天前
    @love Web Component 可以基于 JS 来做啊 然后用 css in js 那样把 template 和 css 包进来
    好处是组件化标签 组件内部不透明 不会收到外部的干扰
        3
    yyfearth   46 天前
    用 import 来导入组件 然后用 webpack 之类的打包在一起
        4
    SilentDepth   46 天前
    想起 Svelte 的作者 Rich Harris 发过的一篇文章:Why I don't use web components ( https://dev.to/richharris/why-i-don-t-use-web-components-2cia)
        5
    vivaxy   46 天前
    @SilentDepth 实际使用中确实会遇到很多问题,尤其是 <link rel="import"> 的用法已经被标准废弃了
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   800 人在线   最高记录 5043   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 19ms · UTC 20:56 · PVG 04:56 · LAX 12:56 · JFK 15:56
    ♥ Do have faith in what you're doing.