vue 组件和web Components的关系

Web Components 是什么

Web Components 是浏览器原生支持的组件化方案,允许你创建自定义、可封装的HTML 标记,使用时不用加载任何额外的模块。自定义组件和小部件基于 Web Components 标准构建,并可与任何支持 HTML 的 JavaScript 库或框架一起使用

Custom elements创建方式

HTML template模版创建
xml 复制代码
<my-component id="myComponent"></my-component>

  <template id="myTemplate">
    <style>
      h1 {
        color: red
      }
    </style>
    <h1 id="title">template 创建</h1>
    </template>


    <script>
    class MyComponent extends HTMLElement {
      constructor() {
        super();
        const template = document.querySelector('#myTemplate')
        const content = template.content.cloneNode(true)
        this.appendChild(content)
      }
    }

customElements.define('my-component', MyComponent)
  </script>
Class函数内部创建
xml 复制代码
<my-element></my-element>

  <script>
  class MyElement extends HTMLElement {
    constructor() {
      super();
      const style = document.createElement('style');
      style.textContent = `
          p {
            color: saddlebrown;
          }
        `;
      const wrapper = document.createElement('p');
      wrapper.textContent = '通过class内创建';
      this.appendChild(style);
      this.appendChild(wrapper);
    }
  }

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


</script>
类型扩展创建 HTMLDivElement - Web API | MDN
xml 复制代码
<div is="expanding-list">
  <div>我是注册自定义元素</div>
</div>
  <script>
  class ExpandingList extends HTMLDivElement {
    constructor() {
      super()
    }

    connectedCallback() {

      const divs = Array.from(this.querySelectorAll('div'))

      setInterval(()=>{
        divs.forEach((div) => {
          div.style.display = 'none'
        })
      }, 1000)
      setInterval(()=>{
        divs.forEach((div) => {
          div.style.display = 'block'
        })
      }, 2000)

    }
  }

customElements.define('expanding-list', ExpandingList, {extends: 'div'})
  </script>

Shadow DOM (隐式 DOM)

kotlin 复制代码
class MyComponent extends HTMLElement {
  constructor() {
    super();
    // closed | open
    const shadowRoot = this.attachShadow({ mode: 'closed' })
    const template = document.querySelector('#myTemplate')
    const content = template.content.cloneNode(true)
    const title = content.querySelector('#title')
    const propTitle = this.getAttribute('title')
    title.innerText = propTitle

    content.querySelector('img').setAttribute('src', this.getAttribute('image'));
    content.querySelector('.name').innerText = this.getAttribute('name');

    shadowRoot.appendChild(content)
  }
}

生命周期

connectedCallback:当自定义元素第一次被连接到文档 DOM 时被调用。

disconnectedCallback:当自定义元素与文档 DOM 断开连接时被调用。

adoptedCallback:当自定义元素被移动到新文档时被调用。

attributeChangedCallback:当自定义元素的一个属性被增加、移除或更改时被调用。

Slot 插槽

xml 复制代码
<template id="myTemplate">
    <style>
      h1 {
        color: red
      }
    </style>
    <h1 id="title">这是一个大标题</h1>
    <slot></slot>
    <slot name="slot1"></slot>
    <slot name="slot2"></slot>
  </template>
  
  <my-component id="myComponent" title="这是传入的标题">
    <h2>插槽slot</h2>
    <h2 slot="slot1">插槽slot1</h2>
    <h2 slot="slot2">插槽slot2</h2>
  </my-component>
  
  <script>
    class MyComponent extends HTMLElement {
      constructor() {
        super();
        const template = document.querySelector('#myTemplate')
        const content = template.content.cloneNode(true)
        const title = content.querySelector('#title')
        // title.addEventListener('click', ()=>{
        //   console.log('内部添加click')
        // })
        this.$title = title
        const propTitle = this.getAttribute('title')
        title.innerText = propTitle
        const shadowRoot = this.attachShadow({ mode: 'open' })
        const myEvent = new CustomEvent('myEvent', {
          detail: '这是子组件传过来的消息'
        })
        title.addEventListener('click', () => {
          this.dispatchEvent(myEvent)
        })
        shadowRoot.appendChild(content)
      }
      static get observedAttributes() {
        return ['title']
      }
      attributeChangedCallback() {
        const propTitle = this.getAttribute('title')
        this.$title.innerText = propTitle
      }
    }
    document.querySelector('#myComponent').addEventListener('click', e => {
      e.target.setAttribute('title', '传入的标题被修改了')
    })
    window.customElements.define('my-component', MyComponent)
  </script>

自定义事件

kotlin 复制代码
<my-component/>
  
  <script>
    // 定义子组件
    class MyComponent extends HTMLElement {
      static get observedAttributes(){
        return ['count']
      }
      get count(){
        return this.getAttribute('count') ? this.getAttribute('count'):0
      }
      set count(val){
        this.setAttribute('count',val)
        this.render();
      }
      
      constructor() {
        super();
        const shadowRoot = this.attachShadow({ mode: 'open' })
        shadowRoot.innerHTML = `
          <style>
            h1 {
              color: red;
              cursor: pointer; /* 指示可点击 */
            }
            h2 {
              color: blue;
              cursor: pointer; /* 指示可点击 */
            }
          </style>
          <h1 id="title">这是一个大标题</h1>
          <h2 id="title1">这是一个大标题1</h2>
          <button id="btnAdd">+</button>
          <h3 id="counter">${this.count}</h3>
          <button id="btnReduce">-</button>
        `
        
        const btn1 = this.shadowRoot.querySelector('#btnAdd')
        btn1.addEventListener('click',  () => this.count++)
        const btn2 = shadowRoot.querySelector('#btnReduce')
        btn2.addEventListener('click',  () => this.count--)
        // 获取模板内容并克隆
        // const template = document.getElementById('myTemplate');
        // const content = template.content.cloneNode(true);
        
        
        // 获取 h1 和 h2 元素
        const title = this.shadowRoot.querySelector('#title');
        const title1 = this.shadowRoot.querySelector('#title1');
        
        // 添加点击事件,触发自定义事件
        title.addEventListener('click', () => {
          this.dispatchEvent(new CustomEvent('h1-click', { detail: 'h1 被点击' }));
        });
        
        title1.addEventListener('click', () => {
          this.dispatchEvent(new CustomEvent('h2-click', { detail: 'h2 被点击' }));
        });
      }
      render() {
        this.shadowRoot.querySelector('#counter').textContent = this.count;
      }
    }
    
    // 注册自定义组件
    window.customElements.define('my-component', MyComponent);
    
  
    window.onload = function() {
      const myComponent = document.querySelector('my-component');

      // 监听 h1 点击事件
      myComponent.addEventListener('h1-click', (event) => {
        console.log('接收到的消息:', event.detail);  // 输出子组件传递的信息
      });
      
      // 监听 h2 点击事件
      myComponent.addEventListener('h2-click', (event) => {
        console.log('接收到的消息:', event.detail);  // 输出子组件传递的信息
      });
    };
  </script>

组件通信

kotlin 复制代码
<parent-component/>
  
  <script>
    
    class ParentComponent extends HTMLElement { 
      static get observedAttributes(){
        return ['childVal']
      }
      
      constructor() {  
        super();  
        this.attachShadow({mode:"open"})
        this.message = 'Hello World!';
      }  
      get childVal(){
        return this.getAttribute('childval'); // 直接从属性中获取值

      }
      set childVal(value){
        this.setAttribute('childval', value); 
        this.render();  
      }
      connectedCallback() {  
        this.render(); 
        this.addEventListener('messageToParent', this.handleMessageFromChild.bind(this));  
      } 
      
      render() {
        this.shadowRoot.innerHTML = `  
          <div>  
            <h1>Parent Component</h1> 
            <div id='childId'>${this.childVal || ''}</div>
            <child-component message="${this.message}"></child-component>  
          </div>  
        `;  
      } 
      attributeChangedCallback(attr, oldValue, newValue){
        onsole.log('attr, oldValue, newValue', attr, oldValue, newValue)
        if(attr == 'childVal'){
          this.render();
        }
      }
      
      handleMessageFromChild(event) {  
        console.log('Received message from child:', event.detail.message);  
        this.childVal = event.detail.message
        // document.getElementById('childId').textContent = event.detail.message
        // 在这里处理来自子组件的消息,例如更新父组件的状态  
      }  
    }  
    customElements.define('parent-component', ParentComponent);
    
    
    
    class ChildComponent extends HTMLElement {  
      static get observedAttributes() {  
        return ['message'];  
      }  
      
      constructor() {  
        super();  
        this.attachShadow({ mode: 'open' });  
        
      }  
      
      connectedCallback() {
        
        this.render();
        const button = this.shadowRoot.querySelector('#btn')
        button.addEventListener('click', this.sendMessageToParent.bind(this));
      }  
      
      attributeChangedCallback(name, oldValue, newValue) {  
        if (oldValue !== newValue) {  
          this.render();  
        }  
      }  
      
      render() {  
        this.shadowRoot.innerHTML = `  
          <div>  
            <h2>Child Component</h2>  
            <button id="btn">Send Message to Parent</button>
            <p>${this.getAttribute('message')}</p>
          </div>  
        `;  
      }  
      
      sendMessageToParent() {
        const customEvent = new CustomEvent('messageToParent', {  
          detail: { message: 'Hello from Child!' },  
          bubbles: true,
          composed: true // 允许事件穿透 Shadow DOM 边界  
        });  
        this.dispatchEvent(customEvent);  
      }  
    }  
    customElements.define('child-component', ChildComponent);
    
  </script>

框架

  • X-Tag: 是微软推出的开源库,支持 Web Components 规范,兼容Web Components API。
  • Omi: 是 Web Components + JSX/TSX 融合为一个框架,小巧的尺寸和高性能,融合和 React 和 Web Components 各自的优势。
  • Slim.js: 是一个开源的轻量级 Web Components 库,它为组件提供数据绑定和扩展能力,使用 es6 原生类继承。专注于帮助开发者更好的编写原生web组件,而不依赖于其他框架,但是也提供了良好的拓展性,开发者可以自由拓展。
  • direflow: 是一个 React组件 + web component +web componen t属性变化重新挂载 React 组件的 web component框架。

参考文档

相关推荐
疏狂难除5 分钟前
基于SeaORM+MySQL+Tauri2+Vite+React等的CRUD交互项目
前端·react.js·前端框架
onejason8 分钟前
如何使用PHP爬虫获取Shopee(虾皮)商品详情?
java·前端
赵大仁11 分钟前
深入解析前后端分离架构:原理、实践与最佳方案
前端·架构
学不动学不明白14 分钟前
PC端项目兼容手机端
前端
无名之逆15 分钟前
Hyperlane:轻量、高效、安全的 Rust Web 框架新选择
开发语言·前端·后端·安全·rust·github·ssl
wkj00121 分钟前
js给后端发送请求的方式有哪些
开发语言·前端·javascript
最新资讯动态28 分钟前
“RdbStore”上线开源鸿蒙社区 助力鸿蒙应用数据访问效率大幅提升
前端
magic 24529 分钟前
JavaScript运算符与流程控制详解
开发语言·前端·javascript
xulihang1 小时前
在手机浏览器上扫描文档并打印
前端·javascript·图像识别
RR91 小时前
【Vue3 进阶👍】:如何批量导出子组件的属性和方法?从手动代理到Proxy的完整指南
前端·vue.js