未来组件化开发趋势WebComponent

未来组件化开发趋势WebComponent

优点:原生组件,不需要框架,性能好代码少。
缺点:兼容性问题

组件化好处: 高内聚、可重用、可组合

https://developer.mozilla.org/zh-CN/docs/Web/API/Web_components

核心三项技术

  • Custom elements:一组JavaScript API,允许您定义custom elements及其行为,然后可以在您的用户界面中按照需要使用它们
  • Shadow DOM:一组JavaScript API,用于将封装的“影子”DOM树附加到元素(与主文档DOM分开呈现)并控制其关联的功能。通过这种方式,您可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。
  • HTML templates<template><slot> 元素使您可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。

WebComponent生命周期

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

案例组件实现

shadowDOM完全隔离

组件间通信,通过dispatchEvent派发自定义监听事件

customEvent -> webcomponent 兼容性差,没有自动更新机制

实现自定义Button组件

index.html
  • 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>
    </head>
    
    <body>
        <style>
            :root{
                --background-color: black;
                --text-color:yellow
            }
        </style>
        <hs-button type="primary">按钮</hs-button>
        <hs-button>按钮</hs-button>
        <!-- 内容是不会被渲染到视图上,不会影响页面展示,可以使用模板 -->
        <template id="btn">
            <button class="hs-button">
                <slot></slot>
            </button>
        </template>
    
        <script>
            class HSButton extends HTMLElement {
                constructor() {
                    super();
                    let shadow = this.attachShadow({ mode: 'open' });
                    let btnTmpl = document.getElementById('btn');
                    let cloneTemplate = btnTmpl.content.cloneNode(true)
                    const style = document.createElement('style');
                    let type = this.getAttribute('type') || 'default';
                    const btnList = {
                        'primary': {
                            background: '#409eff',
                            color: '#fff'
                        },
                        'default': {
                            background: '#909399',
                            color: '#fff'
                        }
                    }
                    style.textContent = `
                        .hs-button{
                            outline:none;
                            border:none;
                            border-radius:4px;
                            padding:5px 20px;
                            display:inline-flex;
                            background:var(--background-color,${btnList[type].background});
                            color:var(--text-color,${btnList[type].color});
                            cursor:pointer
                        }
                    `
                    // dom操作具备移动型
                    shadow.appendChild(style)
                    shadow.appendChild(cloneTemplate)
                }
            }
            // 定义了一个自定义标签 组件
            window.customElements.define('hs-button', HSButton)
        </script>
    </body>
    
    </html>
    

    template中的内容是我们定义的button组件的样子。slot可以获取自定义组件中的内容,插入到模板对应的位置

shadowDOM
shadow DOM 可以实现真正的隔离机制

img

实现自定义Collapse 折叠面板组件

collapse.html
  • collapse.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>
    </head>
    <body>
        <hs-collapse >
            <hs-collapse-item title="Node" name="1">
                <div>nodejs welcome</div>
            </hs-collapse-item>
            <hs-collapse-item title="react" name="2">
                <div>react welcome</div>
            </hs-collapse-item>
            <hs-collapse-item title="vue" name="3">
                <div>vue welcome</div>
            </hs-collapse-item>
        </hs-collapse>
        
        <!-- 没有实际意义, 不会渲染到页面上 -->
        <template id="collapse_tmpl">
            <div class="hs-collapse">
                <slot></slot>
            </div>
        </template>
        <template id="collapse_item_tmpl">
            <div class="hs-collapse-item">
                <div class="title"></div>
                <div class="content">
                    <slot></slot>
                </div>
            </div>
        </template>
        <!-- vite 实现原理 就依赖于 type="module" -->
        <script src="./index.js" type="module"></script>
    </body>
    </html>
    
collapse-item.js
  • collapse-item.js

    class CollapseItem extends HTMLElement {
        constructor() {
            super();
            let shadow = this.attachShadow({ mode: 'open' });
            let tmpl = document.getElementById('collapse_item_tmpl');
            let cloneTemplate = tmpl.content.cloneNode(true);
            let style = document.createElement('style');
            this.isShow = true; // 标识自己是否需要显示
    
            style.textContent = `
                :host{
                    width:100%;
                }
                .title{
                    background:#f1f1f1;
                    line-height:35px;
                    height:35px;
                }
                .content{
                    font-size:14px;
                }
            `
    
            shadow.appendChild(style)
            shadow.appendChild(cloneTemplate);
            this.titleEle = shadow.querySelector('.title');
    
            this.titleEle.addEventListener('click',()=>{
                // 如果将结果传递给父亲  组件通信? 派发一个事件
                document.querySelector('hs-collapse').dispatchEvent(new CustomEvent('changeName',{
                    detail:{
                        name:this.getAttribute('name'),
                        isShow:this.isShow
                    }
                }))
            })
        }
    
        static get observedAttributes() { // 监控属性的变化
            return ['active', 'title', 'name']
        }
        // update
        attributeChangedCallback(key, oldVal, newVal) {
            switch (key) {
                case 'active':
                    this.activeList = JSON.parse(newVal); // 子组件接受父组件的数据
                    break;
                case 'title':
                    this.titleEle.innerHTML = newVal; // 接受到title属性 作为dom的title
                    break;
                case 'name':
                    this.name = newVal
                    break;
            }
            let name = this.name;
            if (this.activeList && name) {
                this.isShow = this.activeList.includes(name);
                this.shadowRoot.querySelector('.content').style.display =  this.isShow ? 'block' : 'none'
            }
        }
    }
    export default CollapseItem
    
collapse.js
  • collapse.js

    class Collapse extends HTMLElement {
        constructor() {
            super();
            const shadow = this.attachShadow({ mode: 'open' });
            const tmpl = document.getElementById('collapse_tmpl');
            let cloneTemplate = tmpl.content.cloneNode(true);
            let style = document.createElement('style');
            // :host 代表的是影子的根元素
            style.textContent = `
                :host{
                    display:flex;
                    border:3px solid #ebebeb;
                    border-radius:5px;
                    width:100%;
                }
                .hs-collapse{
                    width:100%;
                }
            `
            shadow.appendChild(style);
            shadow.appendChild(cloneTemplate);
            // 从影子中拿到插槽
            let slot = shadow.querySelector('slot'); // 监控slot变化  
            // 监听插槽的变化
            slot.addEventListener('slotchange', (e) => {
                this.slotList = e.target.assignedElements();
                this.render();
            })
        }
        static get observedAttributes() { // 监控属性的变化
            return ['active']
        }
        // update 属性变化时执行
        attributeChangedCallback(key, oldVal, newVal) {
            if (key == 'active') {
                this.activeList = JSON.parse(newVal);
                this.render();
            }
        }
        render() {
          // 获取插槽里的元素
            if (this.slotList && this.activeList) {
                [...this.slotList].forEach(child => {
                    child.setAttribute('active', JSON.stringify(this.activeList))
                });
            }
        }
        // connectedCallback(){
        //     console.log('插入到dom时执行的回调')
        // }
        // disconnectedCallback(){
        //     console.log('移除到dom时执行的回调')
        // }
        // adoptedCallback(){
        //     console.log('将组件移动到iframe 会执行')
        // }
    
    }
    export default Collapse
    
index.js
  • index.js

    import Collapse from './collapse.js';
    import CollapseItem from './collapse-item.js';
    
    
    window.customElements.define('hs-collapse',Collapse);
    window.customElements.define('hs-collapse-item',CollapseItem);
    
    
    // 设置组件默认显示的状态 
    
    let defaultActive = ['1','2']; // name:1 name:2 默认展开 3 应该隐藏
    // 拿不到影子里面的东西(也就是template里的)
    document.querySelector('hs-collapse').setAttribute('active',JSON.stringify(defaultActive));
    
    // 每个item需要获取到defaultActive 和自己的name属性比较,如果在里面就显示,不在里面就隐藏
    document.querySelector('hs-collapse').addEventListener('changeName',(e)=>{
        let {isShow,name} = e.detail;
        if(isShow){
            let index = defaultActive.indexOf(name);
            defaultActive.splice(index,1);
        }else{
            defaultActive.push(name);
        }
        document.querySelector('hs-collapse').setAttribute('active',JSON.stringify(defaultActive));
    });
    
    // shadowDOM 完全隔离
    // 组件间的通信 属性,事件
    // customEvent  -> webcomponent 兼容性差,没有自动更新机制
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值