深入理解Web Components

1. 什么是Web Components?它的出现原因?为什么要学习它?

Web Components 是一个浏览器原生支持的组件化方案,允许你创建新的自定义、可封装、可重用的HTML 标记。不用加载任何外部模块,直接就可以在浏览器中跑。

它的出现原因?因为早期组件生态很乱,有各种各样的框架和库,都有各自的规范,导致一个团队切换框架很困难 。为了解决这种分化的形式,让 Web 组件模型统一化,所以才有了Web Components规范的出现。目标是提供一种权威的、浏览器能理解的方式来创建组件。

为什么要学习它?用一句话总结就是回顾历史展望未来。

2011年提出Web Components概念,React诞生

2013年 Chrome 和 Opera 又联合提出了推出的 V0 版本的 Web Components 规范,React开源

2014年Vue诞生

2016年Web Components 推进到了 V1 版本

由于浏览器兼容性、和主流框架开发效率等等问题导致现在几乎使用不到它,但我们可以学习它的思想,也许未来就会变的有用?

2. Web Components 由三种技术组成

Custom Elements

可以创建一个自定义标签。根据规范,自定义元素的名称必须包含连词线”-“,用与区别原生的 HTML 元素。

<body>
    <user-card></user-card>
    <script>
        class UserCard extends HTMLElement {
            constructor() {
                super();
                
                var el = document.createElement('p');
                el.classList.add('name');
                el.innerText = 'User Name';
                
                this.append(el);
            }
        }
        window.customElements.define('user-card', UserCard);
    </script>
</body>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k1Av4lYZ-1621597650365)(/Users/mybells/Desktop/%E5%85%AC%E5%8F%B8%E5%89%8D%E7%AB%AF%E6%96%87%E6%A1%A3%E6%95%B4%E7%90%86/%E5%B7%B2%E5%AE%8C%E6%88%90%E6%96%87%E6%A1%A3%E6%95%B4%E7%90%86/%E5%9B%BE%E7%89%87/webcomponents%E5%AD%A6%E4%B9%A0/image-20210504213119362.png)]

class UserButton extends HTMLButtonElement {
    constructor() {
    	super();
    }
}

customElements.define('user-button', UserButton, { extends: "button" });
<button is="user-button">

</button>
使用生命周期回调函数

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

  • connectedCallback:当 custom element首次被插入文档DOM时,被调用。
  • disconnectedCallback:当 custom element从文档DOM中删除时,被调用。
  • adoptedCallback:当 custom element被移动到新的文档时,被调用。
  • attributeChangedCallback: 当 custom element增加、删除、修改自身属性时,被调用。
Shadow DOM

Shadow DOM是对DOM的一个封装。可以将标记结构、样式和行为隐藏起来,并与页面上的其他代码相隔离,保证不同的部分不会混在一起,可使代码更加干净、整洁。

<style>
    p {
        color: blueviolet;
    }
</style>

<p>不会影响shadow样式</p>
<script>
    const header = document.createElement('header');
    const shadowRoot = header.attachShadow({mode: 'closed'});// header.shadowRoot === null
    // const shadowRoot = header.attachShadow({mode: 'open'});// header.shadowRoot === shadowRoot   	#document-fragment
    shadowRoot.innerHTML = '<p>Hello Shadow DOM</p>';
    document.querySelector('body').append(header);// shadowRoot.host === header
</script>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QMU9AZAI-1621597589953)(/Users/mybells/Desktop/%E5%85%AC%E5%8F%B8%E5%89%8D%E7%AB%AF%E6%96%87%E6%A1%A3%E6%95%B4%E7%90%86/%E5%B7%B2%E5%AE%8C%E6%88%90%E6%96%87%E6%A1%A3%E6%95%B4%E7%90%86/%E5%9B%BE%E7%89%87/%E5%AD%A6%E4%B9%A0%E4%BD%BF%E7%94%A8webcomponents/image-20210506170009819.png)]

修改shadow dom内部样式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-roe3zfOO-1621597589954)(/Users/mybells/Desktop/%E5%85%AC%E5%8F%B8%E5%89%8D%E7%AB%AF%E6%96%87%E6%A1%A3%E6%95%B4%E7%90%86/%E5%B7%B2%E5%AE%8C%E6%88%90%E6%96%87%E6%A1%A3%E6%95%B4%E7%90%86/%E5%9B%BE%E7%89%87/%E5%AD%A6%E4%B9%A0%E4%BD%BF%E7%94%A8webcomponents/image-20210507114101311.png)]

templates and slots

可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。

  1. 被使用前不会被渲染。
  2. 被使用前对页面其他部分没有影响,脚本不会运行,图像不会加载,音频不会播放。
<body>
    <p>会影响外部样式</p>
    <template id="my-paragraph">
        <style>
            p{color: red;}
        </style>
        <p>My paragraph</p>
    </template>
    <my-paragraph></my-paragraph>
    <script>
        customElements.define('my-paragraph',
            class extends HTMLElement {
                constructor() {
                    super();
                    let template = document.getElementById('my-paragraph');
                    let templateContent = template.content.cloneNode(true);

                    this.appendChild(templateContent);
                }
        })
    </script>
</body>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6UQBWgIy-1621597589956)(/Users/mybells/Desktop/%E5%85%AC%E5%8F%B8%E5%89%8D%E7%AB%AF%E6%96%87%E6%A1%A3%E6%95%B4%E7%90%86/%E5%B7%B2%E5%AE%8C%E6%88%90%E6%96%87%E6%A1%A3%E6%95%B4%E7%90%86/%E5%9B%BE%E7%89%87/%E5%AD%A6%E4%B9%A0%E4%BD%BF%E7%94%A8webcomponents/image-20210506170311517.png)]

slot的使用:

<body>
    <style>
        p{color: blueviolet;}
    </style>
    
    
    <p>会影响外部样式</p>
    <template id="my-paragraph">
        <style>
            p{color: red;}
        </style>
        <p>My paragraph</p>
        <slot name="my-text">My default text</slot>
    </template>
    
    
    <my-paragraph>
        <p slot="my-text">slot text</p>
    </my-paragraph>
    
    
    <script>
        customElements.define('my-paragraph',
            class extends HTMLElement {
                constructor() {
                    super();
                    let template = document.getElementById('my-paragraph');
                    let templateContent = template.content.cloneNode(true);
                    this.attachShadow({mode: 'open'}).appendChild(templateContent);
                }
        })
    </script>
</body>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ff0KSkdH-1621597589957)(/Users/mybells/Desktop/%E5%85%AC%E5%8F%B8%E5%89%8D%E7%AB%AF%E6%96%87%E6%A1%A3%E6%95%B4%E7%90%86/%E5%B7%B2%E5%AE%8C%E6%88%90%E6%96%87%E6%A1%A3%E6%95%B4%E7%90%86/%E5%9B%BE%E7%89%87/%E5%AD%A6%E4%B9%A0%E4%BD%BF%E7%94%A8webcomponents/image-20210506172255519.png)]

3. 实现一个计数器组件

<body>
    <template id="button-counter">
        <style>
            p{color: blueviolet;}
        </style>
        
        <span id="counter"></span>
        <button id="button"></button>
    </template>
    <button-counter counter="0" label="增加1"></button-counter>
    <script>
        document.querySelector('button-counter').buttonCounter=(val)=>{}
        
        // document.querySelector('button-counter').addEventListener('test', (val)=>{
        //     console.log(val.detail.counter);
        // })
        
        class ButtonCounter extends HTMLElement {
            constructor() {
                super();
                
                let shadowRoot = this.attachShadow({mode: 'open'});
                let el = document.querySelector('#button-counter').content.cloneNode(true);
                shadowRoot.appendChild(el);
                
                this.$counter = shadowRoot.querySelector('#counter');
                this.$button = shadowRoot.querySelector('#button');
                this.$button.addEventListener('click', () => {
                    this.counter++;
                    this.buttonCounter(this.counter);
                    
                    // this.dispatchEvent(
                    //     new CustomEvent('test', {
                    //         detail: {counter: this.counter},
                    //     })
                    // );
                });
            }
            get label() {
                return this.getAttribute('label');
            }
            set label(value) {
                this.setAttribute('label', value);
            }
            
            get counter() {
                return this.getAttribute('counter');
            }
            set counter(value) {
                this.setAttribute('counter', value);
            }
            
            static get observedAttributes() {
                return ['label', 'counter'];
            }
            attributeChangedCallback(name, oldVal, newVal) {
                console.log('attributeChangedCallback');
                this.render();
            }
            
            render() {
                this.$button.innerHTML = this.label;
                this.$counter.innerHTML = `You clicked me ${this.counter} times`;
            }
            
            connectedCallback() {
                console.log('connectedCallback');
            }
            
            disconnectedCallback() {
                console.log('disconnectedCallback');
            }
            adoptedCallback() {
                console.log('adoptedCallback');
            }
        }
        customElements.define('button-counter', ButtonCounter)
    </script>
</body>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5mJFsmI0-1621597589958)(/Users/mybells/Desktop/%E5%85%AC%E5%8F%B8%E5%89%8D%E7%AB%AF%E6%96%87%E6%A1%A3%E6%95%B4%E7%90%86/%E5%B7%B2%E5%AE%8C%E6%88%90%E6%96%87%E6%A1%A3%E6%95%B4%E7%90%86/%E5%9B%BE%E7%89%87/webcomponents%E5%AD%A6%E4%B9%A0/image-20210505130801797.png)]

4. Web Components vs Vue Components

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BkQAeKfI-1621597589958)(/Users/mybells/Desktop/%E5%85%AC%E5%8F%B8%E5%89%8D%E7%AB%AF%E6%96%87%E6%A1%A3%E6%95%B4%E7%90%86/%E5%B7%B2%E5%AE%8C%E6%88%90%E6%96%87%E6%A1%A3%E6%95%B4%E7%90%86/%E5%9B%BE%E7%89%87/%E5%AD%A6%E4%B9%A0%E4%BD%BF%E7%94%A8webcomponents/image-20210507111257968.png)]

VueWeb Component
data实例属性
propsattributes
watchobservedAttributes
attributeChangedCallback
computedgetters
methodsclass methods
mountedconnectedCallback
destroyeddisconnectedCallback
style scopedtemplate中的style
templatetemplate
computed: {
  name() {
    return this.planet.name || '';
  },
}


get name() {
  return this.planet.name || '';
}
components: { 
  "planet-summary": PlanetSummary
}


customElements.define("planet-summary", PlanetSummary);
watch: {
  altitude(newValue, oldValue) {
    ...
  }
}
  
    
static get observedAttributes() {
  return ['altitude'];
}
attributeChangedCallback(name, oldValue, newValue) {

}
性能比较

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GmIHvlpM-1621597589959)(/Users/mybells/Desktop/%E5%85%AC%E5%8F%B8%E5%89%8D%E7%AB%AF%E6%96%87%E6%A1%A3%E6%95%B4%E7%90%86/%E5%B7%B2%E5%AE%8C%E6%88%90%E6%96%87%E6%A1%A3%E6%95%B4%E7%90%86/%E5%9B%BE%E7%89%87/%E5%AD%A6%E4%B9%A0%E4%BD%BF%E7%94%A8webcomponents/line-stack%20(2)].png)

从上图可以看出在浏览器中组件越多,vue的js计算时间比web components越少,性能越好。原因大概是因为

vue是通过 模板->虚拟DOM->真实DOM 来实现自定义组件的。

web components是在浏览器中通过 自定义组件->Shadow Dom 实现自定义组件的。

导致了浏览器中vue的性能比web components性能优秀。

5. 优点与缺点

  1. 浏览器原生支持,不需要引入额外的第三方库
  2. 语义化
  3. 复用性,移植性高
  4. 不同团队不同项目可以共用组件
缺点
  1. 需要操作DOM
  2. 目前浏览器兼容性、性能方面不够友好
  3. 和外部css交互比较难

6. 浏览器兼容

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rv5ZU0Z0-1621597589960)(/Users/mybells/Desktop/%E5%85%AC%E5%8F%B8%E5%89%8D%E7%AB%AF%E6%96%87%E6%A1%A3%E6%95%B4%E7%90%86/%E5%B7%B2%E5%AE%8C%E6%88%90%E6%96%87%E6%A1%A3%E6%95%B4%E7%90%86/%E5%9B%BE%E7%89%87/%E5%AD%A6%E4%B9%A0%E4%BD%BF%E7%94%A8webcomponents/image-20210510101144361.png)]

兼容性不好的浏览器可以通过引入webcomponentsjs来polyfills。

<!-- load webcomponents bundle, which includes all the necessary polyfills -->
<script src="node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>

<!-- load the element -->
<script type="module" src="my-element.js"></script>

<!-- use the element -->
<my-element></my-element>

7. 基于web components的框架

LitElement

LitElement是一个快速、轻量级的 Web UI 框架。使用 lit-html 来渲染元素。

Polymer

Polymer是一款实用、基于事件驱动、封装性和交互性强的 Web UI 框架。

Omi

Omi是基于 Web 组件的跨框架跨平台框架 。移动端&桌面&小程序。

  • 11
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值