碎片化学习前端之HTML(webComponent)

深巷酒香 / 2023-05-10 / 原文

前言

webComponent 是 HTML5 推出的新特性,为组件化推广奠定基础。

webComponent 基本使用

原生组件,性能较好,但存在兼容性问题。其核心技术有:Custom elements, Shadow DOM, HTML Templates。

Custom elements

JavaScript API,用于定义 custom elements 及其行为。

<m-button type="primary">webComponent</m-button> // 调用

<script type="text/javascript">
class MButton extends HTMLElement {
     constructor() {
     	super();
     }
}
window.customElements.define('m-button', MButton) // 关联 HTML 和 JS,自定义组件
</script>

HTML Templates

<template>, <slot> 元素标记元素结构,并重用元素。

<m-button type="primary">webComponent</m-button>

<template id="m-btn">
    <button class="m-button">
        <slot>Default</slot>
    </button>
</template>

<script type="text/javascript">
class MButton extends HTMLElement {
    constructor() {
        super();

        let btnTmpl = document.getElementById('m-btn') // 定义模板并获取模板
     }
}
</script>

Shadow DOM

JavaScript API,用于封装 Shadow DOM 附加到元素上,并关联其功能,以此保证元素的功能私有,不会与其他元素冲突。

<m-button type="primary">webComponent</m-button>

<template id="m-btn">
    <button class="m-button">
        <slot>Default</slot>
    </button>
</template>

<script type="text/javascript">
class MButton extends HTMLElement {
     constructor() {
        super();

        let btnTmpl = document.getElementById('m-btn') // 定义模板并获取模板

        let shadow = this.attachShadow({ mode: 'open' }) // 配置 devtools 是否可查看 DOM 结构,open / close
        let cBtnTmpl = btnTmpl.content.cloneNode(true) // copy 模板便于重用

     	shadow.appendChild(cBtnTmpl) // 模板挂载 Shadow DOM
     }
}
</script>

webComponent 高阶使用

webComponent 高阶使用主要包括属性设置,样式设置,事件绑定等

webComponent 属性设置

webComponent 在调用组件时进行属性传值,在声明组件时通过 DOM API 获取属性值进行操作。

<m-button type="primary">webComponent</m-button>

<script type="text/javascript">
class MButton extends HTMLElement {
     constructor() {
     	super();
     	
     	let btnTmpl = document.getElementById('m-btn') // 定义模板并获取模板
     	
     	let shadow = this.attachShadow({ mode: 'open' })
     	let cBtnTmpl = btnTmpl.content.cloneNode(true) // copy 模板便于重用
     	
     	let type = this.getAttribute('type') // 读取属性值,进行操作
     	
     	shadow.appendChild(cBtnTmpl) // 模板挂载 Shadow DOM
     }
}
</script>

webComponent 样式设置

  1. template 设置样式

    <m-button type="primary">webComponent</m-button>
    
    <template id="m-btn">
        <style type="text/css"> // 定义模板样式
            .m-button {
                border: none;
                outline: none;
                border-radius: 4px;
                padding: 5px 20px;
            }
        </style>
        <button class="m-button">
            <slot>Default</slot>
        </button>
    </template>
    
    class MButton extends HTMLElement {
        constructor() {
            super();
    
            let btnTmpl = document.getElementById('m-btn') // 定义模板并获取模板
    
            let shadow = this.attachShadow({ mode: 'open' })
            let cBtnTmpl = btnTmpl.content.cloneNode(true) // copy 模板便于重用
    
            let type = this.getAttribute('type') // 读取属性值,进行操作
    
            shadow.appendChild(cBtnTmpl) // 模板挂载 Shadow DOM
        }
    }
    
  2. Shadow DOM 设置样式

    <m-button type="primary">webComponent</m-button>
    
    <template id="m-btn">
        <style type="text/css"> // 定义模板样式
            .m-button {
                border: none;
                outline: none;
                border-radius: 4px;
            padding: 5px 20px;
            }
        </style>
        <button class="m-button">
            // 具名插槽使用:模板内<slot name="btn-text"></slot>,模板外调用时<span slot="btn-text"></span>
            <slot>Default</slot> 
        </button>
    </template>
    
    class MButton extends HTMLElement {
        constructor() {
            super();
    
            let btnTmpl = document.getElementById('m-btn') // 定义模板并获取模板
    
            let shadow = this.attachShadow({ mode: 'open' })
            let cBtnTmpl = btnTmpl.content.cloneNode(true) // copy 模板便于重用
    
            let type = this.getAttribute('type') // 读取属性值,进行操作
    
            const sheets = {
                'primary': {
                    background: '#409EFF',
                    color: '#FFF'
                },
                'success': {
                    background: '#67C23A',
                    color: '#FFF'
                },
                'warning': {
                    background: '#E6A23C',
                    color: '#FFF'
                },
                'danger': {
                    background: '#F56C6C',
                    color: '#FFF'
                },
                'default': {
                    background: '#909399',
                    color: '#FFF'
                },
            }
    
            const style = document.createElement('style')
            style.textContent = `
                .m-button {
                    background: ${sheets[type].background}; // 属性 配置css 样式
                    color: ${sheets[type].color};
                }
            `
    
            shadow.appendChild(style)
            shadow.appendChild(cBtnTmpl) // 模板挂载 Shadow DOM
        }
    }
    

    注意:webComponent 通过 Shadow DOM 隔离以后,外界无法修改样式,只能通过属性或者设置 CSS 变量的方式修改。

  3. css 变量设置样式

    <style type="text/css">
    :root {
        --text-color: #fff;
    }
    </style>
    
    <m-button type="primary">webComponent</m-button>
    
    <template id="m-btn">
        <style type="text/css"> // 定义模板样式
            .m-button {
                border: none;
                outline: none;
                border-radius: 4px;
                padding: 5px 20px;
            }
        </style>
        <button class="m-button">
        <slot>Default</slot>
        </button>
    </template>
    
    class MButton extends HTMLElement {
        constructor() {
            super();
    
            let btnTmpl = document.getElementById('m-btn') // 定义模板并获取模板
    
            let shadow = this.attachShadow({ mode: 'open' })
            let cBtnTmpl = btnTmpl.content.cloneNode(true) // copy 模板便于重用
    
            let type = this.getAttribute('type') // 读取属性值,进行操作
    
            const sheets = {
                'primary': {
                    background: '#409EFF',
                    color: '#FFF'
                },
                'success': {
                    background: '#67C23A',
                    color: '#FFF'
                },
                'warning': {
                    background: '#E6A23C',
                    color: '#FFF'
                },
                danger': {
                    background: '#F56C6C',
                    color: '#FFF'
                },
                'default': {
                    background: '#909399',
                    color: '#FFF'
                },
            }
    
            const style = document.createElement('style')
            style.textContent = `
                .m-button {
                    background: ${sheets[type].background}; // 属性 配置 css 样式
                    color: var(--text-color, ${sheets[type].color}); // css 变量配置 css 样式
                }
            `
    
            shadow.appendChild(style) // 样式挂载至 Shadow DOM
            shadow.appendChild(cBtnTmpl) // 模板挂载至 Shadow DOM
        }
    }
    

注意:通过 templates 设置样式的优先级是高于 css 变量和 Shadow DOM 的。

webComponent 事件绑定

<m-button type="primary">webComponent</m-button>

<template id="m-btn">
    <button class="m-button">
        <slot>Default</slot>
    </button>
</template>

<script type="text/javascript">
class MButton extends HTMLElement {
    constructor() {
     	super();
     	
     	let btnTmpl = document.getElementById('m-btn') // 定义模板并获取模板
     	
     	let shadow = this.attachShadow({ mode: 'open' }) // 配置 devtools 是否可查看 DOM 结构,open / close
     	let cBtnTmpl = btnTmpl.content.cloneNode(true) // copy 模板便于重用
     	
     	cBtnTmpl.querySelector('.m-button').addEventListener('click', this.onClick)
     	
     	shadow.appendChild(cBtnTmpl) // 模板挂载 Shadow DOM
    }
     onClick() {
        alert('click')
    }
}
</script>

后记

webComponent 提供了一种组件定义的方式,但其根本仍是借助 DOM API 进行操作,对于原生开发能力有较高的要求。