【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>
相关推荐
forwardMyLife4 分钟前
element-plus的面包屑组件el-breadcrumb
javascript·vue.js·ecmascript
计算机学姐38 分钟前
基于python+django+vue的影视推荐系统
开发语言·vue.js·后端·python·mysql·django·intellij-idea
luoluoal1 小时前
java项目之基于Spring Boot智能无人仓库管理源码(springboot+vue)
java·vue.js·spring boot
mez_Blog1 小时前
个人小结(2.0)
前端·javascript·vue.js·学习·typescript
深情废杨杨2 小时前
前端vue-插值表达式和v-html的区别
前端·javascript·vue.js
GHUIJS2 小时前
【vue3】vue3.3新特性真香
前端·javascript·vue.js
众生回避2 小时前
鸿蒙ms参考
前端·javascript·vue.js
洛千陨2 小时前
Vue + element-ui实现动态表单项以及动态校验规则
前端·vue.js
GHUIJS3 小时前
【vue3】vue3.5
前端·javascript·vue.js
计算机学姐4 小时前
基于python+django+vue的家居全屋定制系统
开发语言·vue.js·后端·python·django·numpy·web3.py