【Vue.js】了解 Web Component

学习 Web Component 的原因

这是学习 Vue 插槽前的理论铺垫:

  1. 通过 Web Component 的学习, 我们能更好地理解 Vue 插槽设计的初衷和原有的规范准则
  2. Vue 的组件模板系统是参考了 Web Component 的规范来进行上层设计的!

什么是 Web Component?

在了解 Web Component 之前,我们先来聊一下在没有 Web Component 之前页面开发的一些痛点:

  1. 在 ES3,ES5 的年代,JS 内部定义模板困难, 举个例子:
js 复制代码
var title = '我是标题';
var content = '我是内容';

var view = '<div>'
         + '\n'
         + '  <h1>'
         + title
         + '  </h1>'
         + '\n'
         + '  <p>'
         + '\n'
         + content
	       + '  </p>'
         + '\n'
         + '</div>'
  1. 在 ES6 出来了之后,可以使用模板字符串定义视图。虽说方便了不少,但是还是标准规范不统一。(毕竟 JS 这门编程语言实在是太灵活了 😂)
js 复制代码
const title = '我是标题';
const content = '我是内容';

// 我想直接使用面向过程来写
var view = `
  <div>
    <h1>${title}</h1>
    <p>${content}</p>
  </div>
`;

// 我想使用函数式编程来写
function createView(title, content) {
  return `
    <div>
      <h1>${title}</h1>
      <p>${content}</p>
    </div>
  `;
}
createView(title, content);

// 我想用面相对象的方式来写
class ViewCreator {
  constructor(title, content) {
    this.title = title;
    this.content = content;
  }

  createView() {
    const { title, content } = this;
    return `
      <div>
        <h1>${title}</h1>
        <p>${content}</p>
      </div>
    `;
  }
}
const viewCreator = new ViewCreator(title, content);
viewCreator.createView();

而 Web Component 的出现,就是为了统一自定义组件标准的。

Web Components 的优势:

  1. 自定义标签:Web Component API 允许开发者自定义组件标签,然后直接在 HTML 模板中使用
  2. 自定义传导属性:Web Component 可以将标签上面的属性吸收进来,并提供给真实需要渲染组件视图的元素使用
  3. 自定义插槽:【插槽】这个概念最先就是由 Web Component 提出的,它相当于给定了一个视图的预设,并在模板渲染的时候通过 slot="{slotName}"将定义位置的插槽替换掉。
  4. HTML/DOM 原则上的支持:
    • 在原生的 JavaScript 中, HTML / DOM 本身就规范了 Web components (视图的复用规范)
    • HTML / DOM 希望有方案提供给开发者可自定义可重用, 可被浏览器正常解析的标签; 让逻辑样式封装在一个组件中, 最终用自定义标签渲染视图

关于 Web Component 的一些术语

不需要掌握,先混个眼熟 😁

1. window.customElements

window.customElements 是挂载 window 上面一个对象,它里面有一个 define()方法又来自定义元素

2. shadowDOM

shadowDOM (可以理解为影子 DOM), 一个游离在真实 DOM 之外的一个影子 DOM 对象。

  • shadowDOM 不会直接渲染到真实的文档中
  • Web Component 一旦匹配到了 shadowDOM 中的 shadowRoot 中的内容,就会进行渲染
  • 在 HTMLElement 的子类中使用 attchWindow() 方法获取真实的 DOM 元素

Web Components渲染的两种方式

1. 根据自定义模板创建

使用步骤:

  1. 创建 Web Component
js 复制代码
  class MyInfo extends HTMLElement {
    constructor() {
      super();

      this.title = this.textContent;
      this.avatar = this.getAttribute('avatar');
      this.myName = this.getAttribute('name');
      this.age = this.getAttribute('age');
      this.occupation = this.getAttribute('occupation');

      this.init();
    }

    init() {
      const shadowDOM = this.attachShadow({ mode: 'open' });
      shadowDOM.appendChild(this.createDOM());
    }

    createDOM() {
      var oContainer = this.createContainer();
      oContainer.appendChild(this.createTitle());
      oContainer.appendChild(this.createAvatar());
      oContainer.appendChild(this.createName());
      oContainer.appendChild(this.createAge());
      oContainer.appendChild(this.createOccupation());

      return oContainer;
    }

    createContainer() {
      var oContainer = document.createElement('div');
      oContainer.className = 'my-info-container';
      return oContainer;
    }

    createTitle() {
      var oTitle = document.createElement('h1');
      oTitle.className = 'my-info-title';
      oTitle.textContent = this.title;
      return oTitle;
    }

    createAvatar() {
      var oAvatar = document.createElement('div');
      oAvatar.className = 'my-info-avatar';
      oAvatar.innerHTML = `<img style="width: 100px;" src="${this.avatar}" />`
      return oAvatar;
    }

    createName() {
      var oName = document.createElement('p');
      oName.className = 'my-info-name';
      oName.textContent = `Name: ${this.myName}`;
      return oName;
    }

    createAge() {
      var oAge = document.createElement('p');
      oAge.className = 'my-info-age';
      oAge.textContent = `Age: ${this.age}`;
      return oAge;
    }

    createOccupation() {
      var oOccupation = document.createElement('p');
      oOccupation.className = 'my-info-occupation';
      oOccupation.textContent = `Occupation: ${this.occupation}`;
      return oOccupation;
    }
  }
  
  window.customElements.define('my-info', MyInfo);
  1. 使用 Web Component 组件
html 复制代码
<my-info
  avatar="https://p6-passport.byteacctimg.com/img/user-avatar/714b47c68e75465a5c5108aa8a7887b2~80x80.awebp"
  name="Zhangsan"
  age="25"
  occupation="programmer"
>
  lalala
</my-info>

完整代码:

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>
  <my-info
    avatar="https://p6-passport.byteacctimg.com/img/user-avatar/714b47c68e75465a5c5108aa8a7887b2~80x80.awebp"
    name="Zhangsan"
    age="25"
    occupation="programmer"
  >
    lalala
  </my-info>
  
  <script type="text/javascript">
    class MyInfo extends HTMLElement {
      constructor() {
        super();
  
        this.title = this.textContent;
        this.avatar = this.getAttribute('avatar');
        this.myName = this.getAttribute('name');
        this.age = this.getAttribute('age');
        this.occupation = this.getAttribute('occupation');
  
        this.init();
      }
  
      init() {
        const shadowDOM = this.attachShadow({ mode: 'open' });
        shadowDOM.appendChild(this.createDOM());
      }
  
      createDOM() {
        var oContainer = this.createContainer();
        oContainer.appendChild(this.createTitle());
        oContainer.appendChild(this.createAvatar());
        oContainer.appendChild(this.createName());
        oContainer.appendChild(this.createAge());
        oContainer.appendChild(this.createOccupation());
  
        return oContainer;
      }
  
      createContainer() {
        var oContainer = document.createElement('div');
        oContainer.className = 'my-info-container';
        return oContainer;
      }
  
      createTitle() {
        var oTitle = document.createElement('h1');
        oTitle.className = 'my-info-title';
        oTitle.textContent = this.title;
        return oTitle;
      }
  
      createAvatar() {
        var oAvatar = document.createElement('div');
        oAvatar.className = 'my-info-avatar';
        oAvatar.innerHTML = `<img style="width: 100px;" src="${this.avatar}" />`
        return oAvatar;
      }
  
      createName() {
        var oName = document.createElement('p');
        oName.className = 'my-info-name';
        oName.textContent = `Name: ${this.myName}`;
        return oName;
      }
  
      createAge() {
        var oAge = document.createElement('p');
        oAge.className = 'my-info-age';
        oAge.textContent = `Age: ${this.age}`;
        return oAge;
      }
  
      createOccupation() {
        var oOccupation = document.createElement('p');
        oOccupation.className = 'my-info-occupation';
        oOccupation.textContent = `Occupation: ${this.occupation}`;
        return oOccupation;
      }
    }
    
    window.customElements.define('my-info', MyInfo);
  </script>
</body>
</html>

2. 根据已有的模板克隆创建

使用步骤:

  1. 创建一段可以需要被复用的模板 (这里面定义了一些插槽)
html 复制代码
<template id="my-article-template">
  <style>
    h1 {
      color: red;
    }

    h1 .author,
    h1 .date-time {
      font-size: 16px;
      color: #666;
      font-weight: normal;
    }
  </style>
  <div class="my-article">
    <h1 class="my-article-title">
      <slot name="title" class="title"></slot>
      <slot name="author" class="author"></slot>
      <slot name="dateTime" class="date-time"></slot>
    </h1>
    <p class="my-article-content">
      <slot name="content"></slot>
    </p>
  </div>
</template>
  1. 创建 Web Component 组件
js 复制代码
  class MyArticle extends HTMLElement {
    constructor() {
      super();

      this.init();
    }

    init() {
      const shadowDOM = this.attachShadow({ mode: 'open' });
      const template = document.getElementById('my-article-template').content;
      shadowDOM.appendChild(template.cloneNode(true));
    }
  }
  
  window.customElements.define('my-article', MyArticle);
  1. 使用 Web Component 组件
html 复制代码
<my-article>
  <p slot="title">This is my title</p>
  <span slot="author">Author</span>
  <span slot="dateTime"> - 14:15</span>
  <p slot="content">This is my content</p>
</my-article>

完整代码:

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>
  <template id="my-article-template">
    <style>
      h1 {
        color: red;
      }
  
      h1 .author,
      h1 .date-time {
        font-size: 16px;
        color: #666;
        font-weight: normal;
      }
    </style>
    <div class="my-article">
      <h1 class="my-article-title">
        <slot name="title" class="title"></slot>
        <slot name="author" class="author"></slot>
        <slot name="dateTime" class="date-time"></slot>
      </h1>
      <p class="my-article-content">
        <slot name="content"></slot>
      </p>
    </div>
  </template>

  <my-article>
    <p slot="title">This is my title</p>
    <span slot="author">Author</span>
    <span slot="dateTime"> - 14:15</span>
    <p slot="content">This is my content</p>
  </my-article>

  <script type="text/javascript">
    class MyArticle extends HTMLElement {
      constructor() {
        super();
  
        this.init();
      }
  
      init() {
        const shadowDOM = this.attachShadow({ mode: 'open' });
        const template = document.getElementById('my-article-template').content;
        shadowDOM.appendChild(template.cloneNode(true));
      }
    }
    
    window.customElements.define('my-article', MyArticle);
  </script>
</body>
</html>
相关推荐
_codeOH10 小时前
Vue 3 vs React 19:框架还在卷,核心原理就这些
前端·vue.js
英勇无比的消炎药11 小时前
新手必看玩转TinyRobot一定要避开这些坑
前端·vue.js
英勇无比的消炎药12 小时前
别再盲目混用AI组件库和传统组件库差距原来这么大
前端·vue.js
英勇无比的消炎药14 小时前
前端提效神器全新AI组件库TinyRobot改写日常开发模式
前端·vue.js
英勇无比的消炎药14 小时前
前端提效神器TinyRobot
前端·vue.js
CDwenhuohuo14 小时前
uni 背景色渐变 全屏
前端·javascript·vue.js
爱怪笑的小杰杰14 小时前
Vue 项目交付第三方开发,如何隐藏核心 JS 源码?
前端·javascript·vue.js
小二·14 小时前
Vue 3 组合式 API 进阶实战
前端·javascript·vue.js
rising start16 小时前
九、vue3 组件通信:全场景详解
前端·vue.js·typescript
编程技术手记16 小时前
Vue Scoped CSS 与动态创建 DOM 的兼容性问题
前端·css·vue.js