Web Components

Web Components概述

Web Components是一套不同的技术,允许创建可重用的自定义元素,它们的功能封装在代码之外,可以在Web应用中使用。

东巴文(db-w.cn) 认为:Web Components是Web平台原生的组件化解决方案,提供了真正的封装和复用能力。掌握Web Components,能构建跨框架的通用组件。

Web Components特性

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Web Components概述 - 东巴文</title>
    
    <style>
        body {
            font-family: 'Segoe UI', sans-serif;
            padding: 20px;
            background: #f5f5f5;
        }
        
        .container {
            max-width: 800px;
            margin: 0 auto;
            background: white;
            padding: 30px;
            border-radius: 10px;
        }
        
        h1 {
            color: #667eea;
            margin-bottom: 20px;
        }
        
        h2 {
            color: #764ba2;
            margin: 30px 0 15px;
            border-left: 4px solid #764ba2;
            padding-left: 15px;
        }
        
        .component-demo {
            background: #f8f9fa;
            padding: 20px;
            border: 2px solid #ddd;
            margin: 15px 0;
            border-radius: 10px;
        }
        
        .component-demo h3 {
            color: #667eea;
            margin-bottom: 10px;
        }
        
        .feature-list {
            list-style: none;
            padding: 0;
        }
        
        .feature-list li {
            padding: 8px 0;
            border-bottom: 1px solid #eee;
        }
        
        .feature-list li:before {
            content: "✓ ";
            color: #667eea;
            font-weight: bold;
        }
        
        .tech-table {
            width: 100%;
            border-collapse: collapse;
            margin: 20px 0;
        }
        
        .tech-table th,
        .tech-table td {
            padding: 12px;
            text-align: left;
            border: 1px solid #ddd;
        }
        
        .tech-table th {
            background: #667eea;
            color: white;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Web Components概述</h1>
        
        <h2>什么是Web Components</h2>
        
        <div class="component-demo">
            <h3>核心概念</h3>
            <p>Web Components是一套Web平台API,允许创建可重用的自定义HTML元素。它们由三个主要技术组成:</p>
            
            <ul class="feature-list">
                <li><strong>Custom Elements(自定义元素):</strong> 定义新HTML元素的API</li>
                <li><strong>Shadow DOM(影子DOM):</strong> 封装元素内部结构的API</li>
                <li><strong>HTML Templates(HTML模板):</strong> 定义可重用模板的API</li>
            </ul>
        </div>
        
        <h2>Web Components优势</h2>
        
        <table class="tech-table">
            <thead>
                <tr>
                    <th>优势</th>
                    <th>说明</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td><strong>原生支持</strong></td>
                    <td>浏览器原生支持,无需框架</td>
                </tr>
                <tr>
                    <td><strong>封装性</strong></td>
                    <td>Shadow DOM提供样式和结构的封装</td>
                </tr>
                <tr>
                    <td><strong>可复用</strong></td>
                    <td>组件可以在任何地方使用</td>
                </tr>
                <tr>
                    <td><strong>跨框架</strong></td>
                    <td>可以在Vue、React、Angular等框架中使用</td>
                </tr>
                <tr>
                    <td><strong>互操作性</strong></td>
                    <td>与现有HTML元素无缝集成</td>
                </tr>
            </tbody>
        </table>
        
        <h2>浏览器支持</h2>
        
        <div class="component-demo">
            <h3>支持情况</h3>
            <div style="background: #2d2d2d; color: #f8f8f2; padding: 15px; border-radius: 5px; margin: 10px 0;">
                <pre>
// 检查浏览器支持
const supportsCustomElements = 'customElements' in window;
const supportsShadowDOM = 'attachShadow' in Element.prototype;
const supportsTemplates = 'content' in document.createElement('template');

console.log('Custom Elements:', supportsCustomElements);
console.log('Shadow DOM:', supportsShadowDOM);
console.log('HTML Templates:', supportsTemplates);

// 所有现代浏览器都支持Web Components
// Chrome 67+, Firefox 63+, Safari 10.1+, Edge 79+
                </pre>
            </div>
        </div>
        
        <h2>简单示例</h2>
        
        <div class="component-demo">
            <h3>第一个Web Component</h3>
            <div style="background: #2d2d2d; color: #f8f8f2; padding: 15px; border-radius: 5px; margin: 10px 0;">
                <pre>
// 定义自定义元素
class MyComponent extends HTMLElement {
    constructor() {
        super();
        
        // 创建Shadow DOM
        const shadow = this.attachShadow({ mode: 'open' });
        
        // 创建模板
        const template = document.createElement('template');
        template.innerHTML = `
            &lt;style&gt;
                .container {
                    padding: 20px;
                    background: #667eea;
                    color: white;
                    border-radius: 10px;
                }
            &lt;/style&gt;
            &lt;div class="container"&gt;
                &lt;h3&gt;我的第一个Web Component&lt;/h3&gt;
                &lt;slot&gt;&lt;/slot&gt;
            &lt;/div&gt;
        `;
        
        // 添加到Shadow DOM
        shadow.appendChild(template.content.cloneNode(true));
    }
}

// 注册自定义元素
customElements.define('my-component', MyComponent);

// 使用组件
// &lt;my-component&gt;这是组件内容&lt;/my-component&gt;
                </pre>
            </div>
        </div>
        
        <div style="background: #fff3cd; padding: 15px; border-radius: 5px; margin-top: 20px;">
            <strong>东巴文提示:</strong>Web Components是Web平台的原生组件化方案,提供了真正的封装和复用能力。掌握Custom Elements、Shadow DOM和HTML Templates三大核心技术,能构建跨框架的通用组件。
        </div>
    </div>
</body>
</html>

自定义元素

自定义元素是Web Components的核心,允许创建新的HTML元素,扩展HTML词汇表。

自定义元素API

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>自定义元素 - 东巴文</title>
    
    <style>
        body {
            font-family: 'Segoe UI', sans-serif;
            padding: 20px;
            background: #f5f5f5;
        }
        
        .container {
            max-width: 800px;
            margin: 0 auto;
            background: white;
            padding: 30px;
            border-radius: 10px;
        }
        
        h1 {
            color: #667eea;
            margin-bottom: 20px;
        }
        
        h2 {
            color: #764ba2;
            margin: 30px 0 15px;
            border-left: 4px solid #764ba2;
            padding-left: 15px;
        }
        
        .element-demo {
            background: #f8f9fa;
            padding: 20px;
            border: 2px solid #ddd;
            margin: 15px 0;
            border-radius: 10px;
        }
        
        .element-demo h3 {
            color: #667eea;
            margin-bottom: 10px;
        }
        
        .code-block {
            background: #2d2d2d;
            color: #f8f8f2;
            padding: 15px;
            border-radius: 5px;
            overflow-x: auto;
            margin: 10px 0;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>自定义元素</h1>
        
        <h2>创建自定义元素</h2>
        
        <div class="element-demo">
            <h3>基本结构</h3>
            <div class="code-block">
                <pre>
// 创建自定义元素
class MyElement extends HTMLElement {
    constructor() {
        super();
        // 初始化代码
    }
    
    // 生命周期回调
    connectedCallback() {
        // 元素被插入DOM时调用
        console.log('元素已连接到DOM');
    }
    
    disconnectedCallback() {
        // 元素从DOM移除时调用
        console.log('元素已从DOM移除');
    }
    
    adoptedCallback() {
        // 元素被移动到新文档时调用
        console.log('元素已移动到新文档');
    }
    
    attributeChangedCallback(name, oldValue, newValue) {
        // 属性变化时调用
        console.log(`属性 ${name} 从 ${oldValue} 变为 ${newValue}`);
    }
    
    // 监听的属性
    static get observedAttributes() {
        return ['data-value', 'disabled'];
    }
}

// 注册自定义元素
customElements.define('my-element', MyElement);

// 使用
// &lt;my-element data-value="123"&gt;&lt;/my-element&gt;
                </pre>
            </div>
        </div>
        
        <h2>自定义内置元素</h2>
        
        <div class="element-demo">
            <h3>扩展现有元素</h3>
            <div class="code-block">
                <pre>
// 扩展HTMLButtonElement
class MyButton extends HTMLButtonElement {
    constructor() {
        super();
        this.addEventListener('click', () =&gt; {
            alert('自定义按钮被点击!');
        });
    }
    
    connectedCallback() {
        this.style.background = '#667eea';
        this.style.color = 'white';
        this.style.border = 'none';
        this.style.padding = '10px 20px';
        this.style.borderRadius = '5px';
        this.style.cursor = 'pointer';
    }
}

// 注册自定义内置元素
customElements.define('my-button', MyButton, { extends: 'button' });

// 使用
// &lt;button is="my-button"&gt;点击我&lt;/button&gt;
                </pre>
            </div>
        </div>
        
        <h2>属性和属性反射</h2>
        
        <div class="element-demo">
            <h3>属性处理</h3>
            <div class="code-block">
                <pre>
class UserCard extends HTMLElement {
    constructor() {
        super();
        this.attachShadow({ mode: 'open' });
        this.render();
    }
    
    // 定义属性
    static get observedAttributes() {
        return ['name', 'email', 'avatar'];
    }
    
    // Getter和Setter
    get name() {
        return this.getAttribute('name') || '';
    }
    
    set name(value) {
        this.setAttribute('name', value);
    }
    
    get email() {
        return this.getAttribute('email') || '';
    }
    
    set email(value) {
        this.setAttribute('email', value);
    }
    
    // 属性变化回调
    attributeChangedCallback(name, oldValue, newValue) {
        if (oldValue !== newValue) {
            this.render();
        }
    }
    
    connectedCallback() {
        this.render();
    }
    
    render() {
        this.shadowRoot.innerHTML = `
            &lt;style&gt;
                .card {
                    border: 1px solid #ddd;
                    padding: 20px;
                    border-radius: 10px;
                    display: flex;
                    align-items: center;
                    gap: 15px;
                }
                
                .avatar {
                    width: 60px;
                    height: 60px;
                    border-radius: 50%;
                    background: #667eea;
                }
                
                .info h3 {
                    margin: 0 0 5px 0;
                    color: #333;
                }
                
                .info p {
                    margin: 0;
                    color: #666;
                    font-size: 14px;
                }
            &lt;/style&gt;
            &lt;div class="card"&gt;
                &lt;img class="avatar" src="${this.getAttribute('avatar')}" alt="头像"&gt;
                &lt;div class="info"&gt;
                    &lt;h3&gt;${this.name}&lt;/h3&gt;
                    &lt;p&gt;${this.email}&lt;/p&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        `;
    }
}

customElements.define('user-card', UserCard);

// 使用
// &lt;user-card name="张三" email="zhangsan@example.com" avatar="avatar.jpg"&gt;&lt;/user-card&gt;
                </pre>
            </div>
        </div>
        
        <h2>自定义元素方法</h2>
        
        <div class="element-demo">
            <h3>公共方法</h3>
            <div class="code-block">
                <pre>
class CounterElement extends HTMLElement {
    constructor() {
        super();
        this._count = 0;
        this.attachShadow({ mode: 'open' });
        this.render();
    }
    
    // 公共方法
    increment() {
        this._count++;
        this.render();
    }
    
    decrement() {
        this._count--;
        this.render();
    }
    
    reset() {
        this._count = 0;
        this.render();
    }
    
    // Getter
    get count() {
        return this._count;
    }
    
    render() {
        this.shadowRoot.innerHTML = `
            &lt;style&gt;
                .counter {
                    display: flex;
                    align-items: center;
                    gap: 10px;
                    padding: 20px;
                    background: #f8f9fa;
                    border-radius: 10px;
                }
                
                button {
                    padding: 10px 20px;
                    background: #667eea;
                    color: white;
                    border: none;
                    border-radius: 5px;
                    cursor: pointer;
                }
                
                button:hover {
                    background: #764ba2;
                }
                
                .count {
                    font-size: 24px;
                    font-weight: bold;
                    min-width: 50px;
                    text-align: center;
                }
            &lt;/style&gt;
            &lt;div class="counter"&gt;
                &lt;button id="dec"&gt;-&lt;/button&gt;
                &lt;span class="count"&gt;${this._count}&lt;/span&gt;
                &lt;button id="inc"&gt;+&lt;/button&gt;
            &lt;/div&gt;
        `;
        
        // 绑定事件
        this.shadowRoot.getElementById('inc').addEventListener('click', () =&gt; {
            this.increment();
        });
        
        this.shadowRoot.getElementById('dec').addEventListener('click', () =&gt; {
            this.decrement();
        });
    }
}

customElements.define('counter-element', CounterElement);

// 使用
// &lt;counter-element id="myCounter"&gt;&lt;/counter-element&gt;
// document.getElementById('myCounter').increment();
// document.getElementById('myCounter').reset();
                </pre>
            </div>
        </div>
        
        <div style="background: #fff3cd; padding: 15px; border-radius: 5px; margin-top: 20px;">
            <strong>东巴文提示:</strong>自定义元素是Web Components的核心。掌握生命周期回调、属性处理、公共方法等API,能创建功能完整的自定义组件。注意命名规范,必须包含连字符(-)。
        </div>
    </div>
</body>
</html>

Shadow DOM

Shadow DOM提供了真正的封装,将组件的内部结构隐藏起来,避免样式冲突。

Shadow DOM使用

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Shadow DOM - 东巴文</title>
    
    <style>
        body {
            font-family: 'Segoe UI', sans-serif;
            padding: 20px;
            background: #f5f5f5;
        }
        
        .container {
            max-width: 800px;
            margin: 0 auto;
            background: white;
            padding: 30px;
            border-radius: 10px;
        }
        
        h1 {
            color: #667eea;
            margin-bottom: 20px;
        }
        
        h2 {
            color: #764ba2;
            margin: 30px 0 15px;
            border-left: 4px solid #764ba2;
            padding-left: 15px;
        }
        
        .shadow-demo {
            background: #f8f9fa;
            padding: 20px;
            border: 2px solid #ddd;
            margin: 15px 0;
            border-radius: 10px;
        }
        
        .shadow-demo h3 {
            color: #667eea;
            margin-bottom: 10px;
        }
        
        .code-block {
            background: #2d2d2d;
            color: #f8f8f2;
            padding: 15px;
            border-radius: 5px;
            overflow-x: auto;
            margin: 10px 0;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Shadow DOM</h1>
        
        <h2>Shadow DOM基础</h2>
        
        <div class="shadow-demo">
            <h3>创建Shadow DOM</h3>
            <div class="code-block">
                <pre>
// 创建Shadow DOM
const element = document.querySelector('#myElement');
const shadow = element.attachShadow({ mode: 'open' });

// mode: 'open' - 可以通过element.shadowRoot访问
// mode: 'closed' - 无法访问shadowRoot

// 添加内容
shadow.innerHTML = `
    &lt;style&gt;
        .container {
            padding: 20px;
            background: #667eea;
            color: white;
            border-radius: 10px;
        }
    &lt;/style&gt;
    &lt;div class="container"&gt;
        &lt;h3&gt;Shadow DOM内容&lt;/h3&gt;
        &lt;p&gt;这些样式不会影响外部&lt;/p&gt;
    &lt;/div&gt;
`;
                </pre>
            </div>
        </div>
        
        <h2>样式封装</h2>
        
        <div class="shadow-demo">
            <h3>样式隔离示例</h3>
            <div class="code-block">
                <pre>
class StyledComponent extends HTMLElement {
    constructor() {
        super();
        const shadow = this.attachShadow({ mode: 'open' });
        
        shadow.innerHTML = `
            &lt;style&gt;
                /* Shadow DOM内的样式 */
                .button {
                    padding: 10px 20px;
                    background: #667eea;
                    color: white;
                    border: none;
                    border-radius: 5px;
                    cursor: pointer;
                    font-size: 16px;
                }
                
                .button:hover {
                    background: #764ba2;
                }
                
                /* 这些样式不会影响外部 */
                h3 {
                    color: #667eea;
                    margin: 0 0 10px 0;
                }
            &lt;/style&gt;
            
            &lt;div&gt;
                &lt;h3&gt;样式封装示例&lt;/h3&gt;
                &lt;button class="button"&gt;Shadow DOM按钮&lt;/button&gt;
            &lt;/div&gt;
        `;
    }
}

customElements.define('styled-component', StyledComponent);

// 外部样式不会影响Shadow DOM内部
// Shadow DOM内部样式也不会影响外部
                </pre>
            </div>
        </div>
        
        <h2>插槽(Slot)</h2>
        
        <div class="shadow-demo">
            <h3>内容分发</h3>
            <div class="code-block">
                <pre>
class CardComponent extends HTMLElement {
    constructor() {
        super();
        const shadow = this.attachShadow({ mode: 'open' });
        
        shadow.innerHTML = `
            &lt;style&gt;
                .card {
                    border: 1px solid #ddd;
                    border-radius: 10px;
                    overflow: hidden;
                    box-shadow: 0 2px 10px rgba(0,0,0,0.1);
                }
                
                .header {
                    background: #667eea;
                    color: white;
                    padding: 20px;
                }
                
                .body {
                    padding: 20px;
                }
                
                .footer {
                    background: #f8f9fa;
                    padding: 15px;
                    border-top: 1px solid #ddd;
                }
            &lt;/style&gt;
            
            &lt;div class="card"&gt;
                &lt;div class="header"&gt;
                    &lt;slot name="header"&gt;默认标题&lt;/slot&gt;
                &lt;/div&gt;
                &lt;div class="body"&gt;
                    &lt;slot&gt;默认内容&lt;/slot&gt;
                &lt;/div&gt;
                &lt;div class="footer"&gt;
                    &lt;slot name="footer"&gt;默认页脚&lt;/slot&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        `;
    }
}

customElements.define('card-component', CardComponent);

// 使用
/*
&lt;card-component&gt;
    &lt;h2 slot="header"&gt;卡片标题&lt;/h2&gt;
    &lt;p&gt;这是卡片的主要内容&lt;/p&gt;
    &lt;p&gt;可以包含多个段落&lt;/p&gt;
    &lt;button slot="footer"&gt;确定&lt;/button&gt;
&lt;/card-component&gt;
*/
                </pre>
            </div>
        </div>
        
        <h2>样式穿透</h2>
        
        <div class="shadow-demo">
            <h3>外部样式影响Shadow DOM</h3>
            <div class="code-block">
                <pre>
class ThemedComponent extends HTMLElement {
    constructor() {
        super();
        const shadow = this.attachShadow({ mode: 'open' });
        
        shadow.innerHTML = `
            &lt;style&gt;
                :host {
                    display: block;
                    padding: 20px;
                    background: var(--component-bg, #f8f9fa);
                    color: var(--component-color, #333);
                    border-radius: 10px;
                }
                
                :host(.dark) {
                    background: #333;
                    color: white;
                }
                
                :host([disabled]) {
                    opacity: 0.5;
                    pointer-events: none;
                }
                
                /* 穿透到插槽内容 */
                ::slotted(h3) {
                    color: #667eea;
                    margin: 0 0 10px 0;
                }
                
                ::slotted(p) {
                    line-height: 1.6;
                }
            &lt;/style&gt;
            
            &lt;div&gt;
                &lt;slot&gt;&lt;/slot&gt;
            &lt;/div&gt;
        `;
    }
}

customElements.define('themed-component', ThemedComponent);

// 使用CSS变量
/*
&lt;style&gt;
    themed-component {
        --component-bg: #667eea;
        --component-color: white;
    }
&lt;/style&gt;

&lt;themed-component&gt;
    &lt;h3&gt;主题组件&lt;/h3&gt;
    &lt;p&gt;使用CSS变量自定义样式&lt;/p&gt;
&lt;/themed-component&gt;

&lt;themed-component class="dark"&gt;
    &lt;h3&gt;暗色主题&lt;/h3&gt;
    &lt;p&gt;通过class切换主题&lt;/p&gt;
&lt;/themed-component&gt;
*/
                </pre>
            </div>
        </div>
        
        <div style="background: #fff3cd; padding: 15px; border-radius: 5px; margin-top: 20px;">
            <strong>东巴文提示:</strong>Shadow DOM提供了真正的封装,避免样式冲突。使用:host选择器定义组件样式,使用::slotted()穿透插槽内容,使用CSS变量实现主题定制。合理使用open和closed模式。
        </div>
    </div>
</body>
</html>

HTML模板

HTML模板允许定义可重用的HTML结构,在需要时实例化。

模板使用

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>HTML模板 - 东巴文</title>
    
    <style>
        body {
            font-family: 'Segoe UI', sans-serif;
            padding: 20px;
            background: #f5f5f5;
        }
        
        .container {
            max-width: 800px;
            margin: 0 auto;
            background: white;
            padding: 30px;
            border-radius: 10px;
        }
        
        h1 {
            color: #667eea;
            margin-bottom: 20px;
        }
        
        h2 {
            color: #764ba2;
            margin: 30px 0 15px;
            border-left: 4px solid #764ba2;
            padding-left: 15px;
        }
        
        .template-demo {
            background: #f8f9fa;
            padding: 20px;
            border: 2px solid #ddd;
            margin: 15px 0;
            border-radius: 10px;
        }
        
        .template-demo h3 {
            color: #667eea;
            margin-bottom: 10px;
        }
        
        .code-block {
            background: #2d2d2d;
            color: #f8f8f2;
            padding: 15px;
            border-radius: 5px;
            overflow-x: auto;
            margin: 10px 0;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>HTML模板</h1>
        
        <h2>template元素</h2>
        
        <div class="template-demo">
            <h3>定义模板</h3>
            <div class="code-block">
                <pre>
&lt;!-- 定义模板 --&gt;
&lt;template id="cardTemplate"&gt;
    &lt;style&gt;
        .card {
            border: 1px solid #ddd;
            border-radius: 10px;
            padding: 20px;
            margin: 10px 0;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        
        .card h3 {
            color: #667eea;
            margin: 0 0 10px 0;
        }
        
        .card p {
            color: #666;
            margin: 0;
        }
    &lt;/style&gt;
    
    &lt;div class="card"&gt;
        &lt;h3&gt;&lt;/h3&gt;
        &lt;p&gt;&lt;/p&gt;
    &lt;/div&gt;
&lt;/template&gt;

&lt;!-- 使用模板 --&gt;
&lt;div id="cardContainer"&gt;&lt;/div&gt;

&lt;script&gt;
// 获取模板
const template = document.getElementById('cardTemplate');
const container = document.getElementById('cardContainer');

// 克隆模板
const clone = template.content.cloneNode(true);

// 填充内容
clone.querySelector('h3').textContent = '卡片标题';
clone.querySelector('p').textContent = '这是卡片内容';

// 添加到DOM
container.appendChild(clone);
&lt;/script&gt;
                </pre>
            </div>
        </div>
        
        <h2>slot元素</h2>
        
        <div class="template-demo">
            <h3>命名插槽</h3>
            <div class="code-block">
                <pre>
&lt;!-- 定义带插槽的模板 --&gt;
&lt;template id="articleTemplate"&gt;
    &lt;style&gt;
        .article {
            border: 1px solid #ddd;
            border-radius: 10px;
            overflow: hidden;
        }
        
        .header {
            background: #667eea;
            color: white;
            padding: 20px;
        }
        
        .content {
            padding: 20px;
        }
        
        .footer {
            background: #f8f9fa;
            padding: 15px;
            border-top: 1px solid #ddd;
        }
    &lt;/style&gt;
    
    &lt;article class="article"&gt;
        &lt;header class="header"&gt;
            &lt;slot name="title"&gt;默认标题&lt;/slot&gt;
        &lt;/header&gt;
        &lt;div class="content"&gt;
            &lt;slot name="content"&gt;默认内容&lt;/slot&gt;
        &lt;/div&gt;
        &lt;footer class="footer"&gt;
            &lt;slot name="footer"&gt;默认页脚&lt;/slot&gt;
        &lt;/footer&gt;
    &lt;/article&gt;
&lt;/template&gt;

&lt;!-- 使用 --&gt;
&lt;custom-article&gt;
    &lt;h2 slot="title"&gt;文章标题&lt;/h2&gt;
    &lt;p slot="content"&gt;文章内容&lt;/p&gt;
    &lt;small slot="footer"&gt;发布时间: 2024-01-01&lt;/small&gt;
&lt;/custom-article&gt;
                </pre>
            </div>
        </div>
        
        <h2>在Web Components中使用模板</h2>
        
        <div class="template-demo">
            <h3>模板与组件结合</h3>
            <div class="code-block">
                <pre>
// 定义模板
const template = document.createElement('template');
template.innerHTML = `
    &lt;style&gt;
        .product-card {
            border: 1px solid #ddd;
            border-radius: 10px;
            overflow: hidden;
            transition: transform 0.3s;
        }
        
        .product-card:hover {
            transform: translateY(-5px);
        }
        
        .product-image {
            width: 100%;
            height: 200px;
            object-fit: cover;
        }
        
        .product-info {
            padding: 15px;
        }
        
        .product-title {
            font-size: 18px;
            font-weight: bold;
            margin: 0 0 10px 0;
            color: #333;
        }
        
        .product-price {
            font-size: 20px;
            color: #667eea;
            font-weight: bold;
        }
        
        .product-button {
            width: 100%;
            padding: 10px;
            background: #667eea;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            margin-top: 10px;
        }
        
        .product-button:hover {
            background: #764ba2;
        }
    &lt;/style&gt;
    
    &lt;div class="product-card"&gt;
        &lt;img class="product-image" src="" alt=""&gt;
        &lt;div class="product-info"&gt;
            &lt;h3 class="product-title"&gt;&lt;/h3&gt;
            &lt;div class="product-price"&gt;&lt;/div&gt;
            &lt;button class="product-button"&gt;加入购物车&lt;/button&gt;
        &lt;/div&gt;
    &lt;/div&gt;
`;

class ProductCard extends HTMLElement {
    constructor() {
        super();
        this.attachShadow({ mode: 'open' });
        this.shadowRoot.appendChild(template.content.cloneNode(true));
    }
    
    static get observedAttributes() {
        return ['image', 'title', 'price'];
    }
    
    attributeChangedCallback(name, oldValue, newValue) {
        if (name === 'image') {
            this.shadowRoot.querySelector('.product-image').src = newValue;
        } else if (name === 'title') {
            this.shadowRoot.querySelector('.product-title').textContent = newValue;
        } else if (name === 'price') {
            this.shadowRoot.querySelector('.product-price').textContent = `¥${newValue}`;
        }
    }
    
    connectedCallback() {
        this.shadowRoot.querySelector('.product-button').addEventListener('click', () =&gt; {
            this.dispatchEvent(new CustomEvent('add-to-cart', {
                detail: {
                    title: this.getAttribute('title'),
                    price: this.getAttribute('price')
                },
                bubbles: true
            }));
        });
    }
}

customElements.define('product-card', ProductCard);

// 使用
/*
&lt;product-card
    image="product.jpg"
    title="商品名称"
    price="99.99"&gt;
&lt;/product-card&gt;
*/
                </pre>
            </div>
        </div>
        
        <div style="background: #fff3cd; padding: 15px; border-radius: 5px; margin-top: 20px;">
            <strong>东巴文提示:</strong>HTML模板提供了高效的DOM复用机制。使用template元素定义模板,通过cloneNode(true)克隆模板内容。结合Web Components,能创建可复用、高性能的组件。
        </div>
    </div>
</body>
</html>

学习检验

完成本章学习后,请尝试回答以下问题:

  1. 选择题: Web Components由哪三个主要技术组成?

    • A. Custom Elements, Shadow DOM, HTML Templates
    • B. Components, Props, State
    • C. Modules, Classes, Inheritance
    • D. Templates, Slots, Events
  2. 填空题: Shadow DOM的mode参数可以是____或____。

  3. 简答题: Shadow DOM的作用是什么?它如何实现样式封装?

  4. 实践题: 创建一个自定义的倒计时组件,要求:

    • 使用Custom Elements API
    • 使用Shadow DOM封装样式
    • 支持设置倒计时时间
    • 倒计时结束时触发事件
  5. 应用题: 比较Web Components与Vue/React组件的异同,分析各自的适用场景。