关于前端:WebComponents使用以及思考

7次阅读

共计 6940 个字符,预计需要花费 18 分钟才能阅读完成。

起因

说起来这个货色, 进去至多 2 年了, 然而因为最近两年我根本没做过 web 端的货色, 所以也没怎么解过了, 趁着这次放假, 补充一下知识点

先来看看 MDN 中对于他的形容:

Web Components 旨在解决这些问题 — 它由三项次要技术组成,它们能够一起应用来创立封装性能的定制元素,能够在你喜爱的任何中央重用,不用放心代码抵触。

  • Custom elements(自定义元素):一组 JavaScript API,容许您定义 custom elements 及其行为,而后能够在您的用户界面中依照须要应用它们。
  • Shadow DOM(影子 DOM):一组 JavaScript API,用于将封装的“影子”DOM 树附加到元素(与主文档 DOM 离开出现)并管制其关联的性能。通过这种形式,您能够放弃元素的性能公有,这样它们就能够被脚本化和款式化,而不必放心与文档的其余局部发生冲突。
  • HTML templates(HTML 模板): <template><slot> 元素使您能够编写不在出现页面中显示的标记模板。而后它们能够作为自定义元素构造的根底被屡次重用。

Custom elements(自定义元素)

Web Components 规范十分重要的一个个性是,它使开发者可能将 HTML 页面的性能封装为 custom elements(自定义标签),而平常,开发者不得不写一大堆简短、深层嵌套的标签来实现同样的页面性能。

CustomElementRegistry

用来解决 web 文档中的 custom elements — 该对象容许你注册一个 custom element,返回已注册 custom elements 的信息,等等。

共有两种 custom elements:

  • Autonomous custom elements 是独立的元素,它不继承其余内建的 HTML 元素。你能够间接把它们写成 HTML 标签的模式,来在页面上应用。例如 <popup-info>,或者是 document.createElement("popup-info") 这样。
  • Customized built-in elements 继承自根本的 HTML 元素。在创立时,你必须指定所需扩大的元素(正如下面例子所示),应用时,须要先写出根本的元素标签,并通过 is 属性指定 custom element 的名称。例如<p is="word-count">, 或者 document.createElement("p", { is: "word-count"})

customElements.define

用来注册一个 custom element,该办法承受以下参数

  • 示意所创立的元素名称的合乎 DOMString 规范的字符串。留神,custom element 的名称不能是单个单词,且其中 必须要有短横线。
  • 用于定义元素行为的 类。
  • 可选参数,一个蕴含 extends 属性的配置对象,是可选参数。它指定了所创立的元素继承自哪个内置元素,能够继承任何内置元素。

小栗子:

<div is="my-name"></div>
<gender-info></gender-info>
<script>
    // 根底 HTML 的元素
    class Names extends HTMLDivElement {constructor() {super();
            // this 指向的就是以后元素
            this.innerText = 'Grewer wrote example'
        }
    }

    customElements.define('my-name', Names, {extends: 'div'});


    // 自定义标签:
    class Gender extends HTMLElement {constructor() {
            // 必须首先调用 super 办法
            super();

            this.innerText = 'Grewer gender is male'

            // 增加办法
            this.onclick = () => {console.log('run in customElement')
            }
        }
    }

    customElements.define('gender-info', Gender)
</script>

页面的显示:

DOM 中的显示:

组件名留神点

  • 必须有 “-” 如果组件名是 gender 则间接报错, 即便是这样的名称也能够 “gender-“
  • 首字母也不能大写, 比方就不能写成 Gender-info

生命周期

在 custom element 的构造函数中,能够指定多个不同的回调函数,它们将会在元素的不同生命期间被调用:

  • connectedCallback:当 custom element 首次被插入文档 DOM 时,被调用。
  • disconnectedCallback:当 custom element 从文档 DOM 中删除时,被调用。
  • adoptedCallback:当 custom element 被挪动到新的文档时,被调用。
  • attributeChangedCallback: 当 custom element 减少、删除、批改本身属性时,被调用。

大部分生命周期和其余框架的相似, 然而其中有一个 attributeChangedCallback 须要阐明下:


<life-test></life-test>

<script>

    class Life extends HTMLElement {
        // 用来搭配 attributeChangedCallback, 管制要监听的具体属性
        static get observedAttributes() {return ['style', 'test'];
        }

        constructor() {super();
            this.innerText = 'life test  click'
            this.onclick = this.change
        }

        change = () => {console.log('add run')
            this.style.background = `rgba(0, 0, 0, ${Math.random()})`
            this.setAttribute('test', Math.random() * 100)
        }
        
        attributeChangedCallback(...arg) {
            // 打印的值别离是: 属性值名, 旧值, 新值  如果没有就为 null
            // 如果同时扭转了 2 个属性, 则触发此函数两次
            console.log('changed', arg)
        }
    }

    customElements.define('life-test', Life)
    
</script>

想要应用 attributeChangedCallback 生命周期, 就必须搭配上 observedAttributes
在线查看上述 dom: 点击查看

shadow DOM

Web components 的一个重要属性是封装——能够将标记构造、款式和行为暗藏起来,并与页面上的其余代码相隔离,保障不同的局部不会混在一起,可使代码更加洁净、整洁。其中,Shadow DOM 接口是关键所在,它能够将一个暗藏的、独立的 DOM 附加到一个元素上。

有了组件的定义之后, 当然少不了 scoped 的掌控, 而 shadow DOM 就是用来做这个的

Shadow DOM 容许将暗藏的 DOM 树附加到惯例的 DOM 树中——它以 shadow root 节点为起始根节点,在这个根节点的下方,能够是任意元素,和一般的 DOM 元素一样。

这里,有一些 Shadow DOM 特有的术语须要咱们理解:

  • Shadow host:一个惯例 DOM 节点,Shadow DOM 会被附加到这个节点上。
  • Shadow tree:Shadow DOM 外部的 DOM 树。
  • Shadow boundary:Shadow DOM 完结的中央,也是惯例 DOM 开始的中央。
  • Shadow root: Shadow tree 的根节点。

根本应用:

<div id="foo"></div>
<body>
<script>
    const div = document.getElementById('foo')
    // 将一般 dom 转换为 shadow dom
    let shadow = div.attachShadow({mode: 'open'});


    // 获取 shadow dom 对象
    // 如果 mode: 'open' 则可能失常获取, 如果为 closed  则 shadowObj 是 null
    const shadowObj = div.shadowRoot
    console.log(shadowObj)


    const p = document.createElement('p')
    p.textContent = 'this is p'
    // 对于 textContent 和 innerText 的区别: https://developer.mozilla.org/zh-CN/docs/Web/API/Node/textContent#%E4%B8%8E_innertext_%E7%9A%84%E5%8C%BA%E5%88%AB

    shadow.append(p)
</script>

我感觉有余的中央在于, 只有在 dom 创立结束之后在能够将他转变成 shadow dom, 而不是在浏览器的创立阶段

增加 styles

<style>
    p{background-color: #444444;}
</style>
<div id="foo"></div>
<p> 不在 shadow 中的 p</p>
<body>
<script>
    // 省略前一步的代码

    const style = document.createElement('style');

    style.textContent =  `
        p {background-color: #2c9edd;}
    `
    shadow.append(style)

    // 通过运行能够看到 p 的款式有了一个 scoped
</script>

在线查看: 点击查看

当然咱们也能够应用内部款式:

// 将内部援用的款式增加到 Shadow DOM 上
const linkElem = document.createElement('link');
linkElem.setAttribute('rel', 'stylesheet');
linkElem.setAttribute('href', 'style.css');

// 将所创立的元素增加到 Shadow DOM 上
shadow.appendChild(linkElem);

Shadow Dom 和 Custom Element 搭配应用

<life-test></life-test>
<script>
  class Life extends HTMLElement {constructor() {super();
      const shadow = this.attachShadow({mode: 'closed'});
        
      const p = document.createElement('p')
      p.textContent = 'click me'
      shadow.append(p)
      this.p = p

      this.onclick = this.change
    }

    change = () => {console.log('add run')
      this.p.style.background = `rgba(0, 0, 0, ${Math.random()})`
      this.p.setAttribute('test', Math.random() * 100)
    }

    connectedCallback() {console.log('connectedCallback', '初始化')
    }

  }

  customElements.define('life-test', Life)
</script>

在线查看: 点击查看

templates and slots

templates

顾名思义也就是模板, 最根本的应用:

失常状况下 template 并不会显示在网页中

<template id="my-paragraph">
    <p>My paragraph</p>
</template>
<p>2 秒钟后执行插入 </p>
</body>
<script>
    function show() {let template = document.getElementById('my-paragraph');
        let templateContent = template.content;
        document.body.appendChild(templateContent);
    }
    // 这里咱们就是要拿到模板中的内容 插入到网页
    // 就像框架中的组件的应用
    setTimeout(show, 2000)
</script>

slot

浏览器对这个性能的反对比 <template> 少,在 Chrome 53, Opera 40, Safari 10, 火狐 59 和 Edge 79 中反对

shadow dom 和 template,slot 的合并应用

<template id="my-paragraph">
    <p>
        上面会显示插槽的内容:
        <slot name="my-text"></slot>
    </p>
</template>
<div id="container">
    <span slot="my-text">Let's have some different text!</span>
</div>
<script>
    let template = document.getElementById('my-paragraph');
    let templateContent = template.content;

    const shadowRoot = document.getElementById('container').attachShadow({mode: 'open'})
        .appendChild(templateContent.cloneNode(true));
</script>

在线查看: 点击查看

这里的 slot 和 Vue 框架中的根本差不多

齐全 WebComponents 应用:

咱们再深刻一步, 加上 Custom Element, 组成齐全的 Web Components

<template id="my-paragraph">
    <p>
        上面会显示插槽的内容:
        <slot name="my-text"></slot>
    </p>
</template>
<my-paragraph>
    <span slot="my-text">Let's have some different text!</span>
</my-paragraph>
<script>
    customElements.define('my-paragraph',
        class extends HTMLElement {constructor() {super();
                let template = document.getElementById('my-paragraph');
                let templateContent = template.content;

                const shadowRoot = this.attachShadow({mode: 'open'})
                    .appendChild(templateContent.cloneNode(true));
            }
        })
</script>

在线查看: 点击查看

查看 Chrome 中的 dom 显示更加有利于了解:

兼容以及 polyfill

说到这, 咱们要说下兼容问题了, 通过 caniuse 咱们能够查看他的兼容水平:



能够看到, 除了 slot 之外, 其余的兼容都曾经到了 94% 以上, 当然遗憾的是没有 ie11

slot 的兼容稍差, 但也有 92%:

polyfill

如果对于上述的兼容还不够完满, 咱们还有 polyfill 来帮忙你:

这是 webComponents 的 polyfill 仓库: https://github.com/webcompone…

应用了 polyfill 之后, 兼容水平当初是这样:

Polyfill Edge IE11+ Chrome* Firefox* Safari 9+* Chrome Android* Mobile Safari*
Custom Elements
Shady CSS/DOM

* 示意浏览器的以后版本

该 polyfill 可能在较旧的浏览器中工作,然而须要应用其余的 polyfill(例如 classList 或其余平台的 polyfill)。咱们不能保障在兼容性列表之外对浏览器的反对。

当初加上了 polyfill 之后, 他的兼容就比拟稳当了

框架拓展

上面列出了几个基于 WebComponents 的框架, 有趣味的能够看一看:

  • LitElement (Google)
  • Fast (Microsoft)
  • LWC (salesforce)
  • Stencil
  • Cybernetically enhanced web apps

结语

webComponents 展现了一套全面的原生反对组件计划, 然而呢, 因为当初框架的倒退, 想要在原生反对上在下功夫, 就比拟麻烦了
对于咱们开发者来说, 当初有 3 种抉择

  1. 从 0 开始, 基于原生 WebComponents 应用或者搭建根底
  2. 关注大框架(如 Vue/React), 对于此 API 的应用偏向以及过程
  3. 尝试应用我下面展现的一些基于 WebComponents 的框架

不过看了网上的一些评论还是有一些痛点在的

在作者写此文的时候, 有一个想法 — WebComponents 应该也能够是一种微前端的实现计划

这个话题还能够持续延长上来, 有趣味我会在前面讲述

参考:

  • https://developer.mozilla.org…
  • https://www.zhihu.com/questio…
正文完
 0