Web Components使用

·  阅读 113

这是我参与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/…

W3C: www.w3.org/html/ig/zh/…

web componets 实例代码:github.com/mdn/web-com…

HTML5 Rocks: www.html5rocks.com/en/tutorial…

阮一峰:javascript.ruanyifeng.com/htmlapi/web…

知乎:zhuanlan.zhihu.com/p/64619005

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改