Web Component

Web Component 最早的概念可以追溯到 2011 年,到了 2018 年 V1 版本开始被主流浏览器所支持(除了 IE)。到了现在,几乎所有的浏览器都支持了 Web Component。

什么是 Web Component

MDN 上面的解释是 Web Component 是一套不同的技术,允许你创建可重用的定制元素(它们的功能封装在你的代码之外)并且在你的 web 应用中使用它们。

其实挺语义化的,就是组件的意思。相对于 react 和 vue 可以很容易的创建一些组件,原生的最早之前对组件的概念非常弱。Web Component 的一个愿景就是可以使用原生的创建组件,跨越不同团队不同项目不同框架共用组件。

现阶段,有蛮多在使用原生的 Web Component,比如 YouToBe、网页 Photoshop、Microsoft 等。一直在开发业务,没机会使用 Web Component 在实践中开发,只能是了解一下这个概念。

Web Component 三个最核心的概念:

  • Custom Elements:自定义元素,一组 JavaScript API,允许你定义 custom elements 及其行为,然后可以在你的用户界面中按照需要使用它们。
  • Shadow DOM:影子 DOM,一组 JavaScript API,用于将封装的"影子"DOM 树附加到元素(与主文档 DOM 分开呈现)并控制其关联的功能。通过这种方式,你可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。
  • HTML Templates:HTML 模板,template 和 slot 元素使你可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。

创建组件,最重要的一些因素:样式隔离、组件传值、回调、生命周期,Web Component 都支持,下面实现一个简单的 button 组件。

load.js

javascript 复制代码
export default (url) => {
  return new Promise((resolve, reject) => {
    var xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4 && xhr.status === 200) {
        resolve(xhr.responseText);
      }
    };
    xhr.send();
  });
};

button.js

javascript 复制代码
import loadTemplate from'./load.js'

const templateContent = `
  <style>
    button{
      color: rgba(255, 0, 0, 0.44);
      background: #fff;
      border: solid 1px rgba(255, 0, 0, 0.44);
      border-radius: 5px;
      line-height: 30px;
    }
  </style>
  <button><slot></slot></button>
`

class Button extends HTMLElement {
  constructor() {
    super();
    loadTemplate('./button.html').then(res => {
      this.attachShadow({ mode: 'open' });
      const template = document.createElement('template');
      //res换成templateContent也可以
      template.innerHTML = res;
      const content = template.content.cloneNode(true);
      this.shadowRoot.appendChild(content);
      this.$button = this.shadowRoot.querySelector('button');

      this.$button.addEventListener('click', () => {
        this.dispatchEvent(new CustomEvent('onCustomClick', {
          detail: '这是子组件的数据'
        }))
      });
    })
  }
  connectedCallback(){
    console.log('生命周期connectedCallback');
  }
  disconnectedCallback(){
    console.log('生命周期disconnectedCallback');
  }
  adoptedCallback(){
    console.log('生命周期adoptedCallback');
  }
  attributeChangedCallback(name, oldVal, newVal){
    console.log('生命周期attributeChangedCallback', name, oldVal, newVal);
  }
  static get observedAttributes() {
    return ['text', 'class'];
  }
}
customElements.define('w-button', Button);

button.html

xml 复制代码
<style>
  button{
    color: rgba(255, 0, 0, 0.44);
    background: #fff;
    border: solid 1px rgba(255, 0, 0, 0.44);
    border-radius: 5px;
    line-height: 30px;
  }
</style>
<button>
  <slot></slot>
  <!-- <slot name="real"></slot> -->
</button>

index.html 使用

xml 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="./button.js" type="module"></script>
</head>
<body>
  <w-button id="wButton" onclick="parentClick()" text="测试" class="is-class">
    <span>这是组件button按钮</span>
    <!-- <span slot="real">具名slot</span> -->
  </w-button>
  <button>这不是组件button</button>
  <button onclick="removeButton()">隐藏</button>
  <button onclick="attributeChange('text')">改变text属性</button>
  <button onclick="attributeChange('class')">改变class属性</button>
</body>
<script>
function test(){
  console.log('这是组件button按钮');
}

const wButton = document.getElementById('wButton');

const removeButton = () => {
  wButton.remove();
}

const attributeChange = (key) => {
  if(key == 'class'){
    wButton.setAttribute('class', 'is-测试');
  }else{
    wButton.setAttribute('text', JSON.stringify({a: 10}));
  }
}

const parentClick = () => {
  console.log('父组件直接触发');
}

wButton.addEventListener('onCustomClick', e => console.log(e));
</script>
</html>

tips

  • 把样式和标签跟 js 分开,参考的是 jQuery 的 load 方法,其实就是用 xhr 加载。如果不分开,可以在 js 里面用字符串模板实现,外部加载可以更好的编写标签和样式,如果使用 webpack、vite 等,可能更容易实现
  • 样式和标签隔离,Shadow DOM 天然支持,实现的非常的好
  • 组件传值,可以用属性,static get observedAttributes()一定要定义,父组件可以改变组件的值,子组件可以监听值的变化,实现子组件根据数据处理。传递的数据,如果是对象,需要序列化一下,因为只能传递字符串
  • 如果单纯的组件样式,触发点击事件可以直接 onclick 绑定,如果是子组件回调,最好使用 new CustomEvent 创建,父组件通过监听获取回调的数据
  • new CustomEvent 一定要把数据放在 detail 里面,可以是任意数据类型
  • slot 也有具名和不具名,具名插槽在父组件使用正常的标签
  • 生命周期直接声明就监听了,adoptedCallback 不知道怎么实现

Web Component 也有一些限制,比如 css 隔离的太彻底,和外部交互比较困难,要做双向数据绑定也比较不容易。不过都不算问题,现在已经有很多实现了 Web Component 的库,可以轻松的使用和构建 Web Component,那些问题都容易解决。个人觉得,Web Component 最大的意义是,可以在不同的框架之间共享组件,比如 Vue、React、Angular,这些框架都有各自的组件,但是 Web Component 的组件可以跨框架使用。

需要意识到,组件化,一般是在一些复用、数据结构稍微复杂的项目使用,组件的目的就是把整体的复杂性解构,从而提高开发效率和可维护性。如果是一些静态为主,加上一些少量交互,就没必要了,反而更不适用。

都 2023 年了,为什么没有哪一家大公司牵头创建 Web Component 生态和推广 Web Component 呢?

欢迎关注订阅号 coding 个人笔记

相关推荐
谁呛我名字1 小时前
大数据应用开发——数据可视化
javascript·vue.js·echarts
前端郭德纲1 小时前
浅谈React的虚拟DOM
前端·javascript·react.js
2401_879103682 小时前
24.11.10 css
前端·css
ComPDFKit3 小时前
使用 PDF API 合并 PDF 文件
前端·javascript·macos
yqcoder3 小时前
react 中 memo 模块作用
前端·javascript·react.js
谈谈叭4 小时前
Javascript中的深浅拷贝以及实现方法
开发语言·javascript·ecmascript
优雅永不过时·4 小时前
Three.js 原生 实现 react-three-fiber drei 的 磨砂反射的效果
前端·javascript·react.js·webgl·threejs·three
爱编程的鱼5 小时前
javascript用来干嘛的?赋予网站灵魂的语言
开发语言·javascript·ecmascript
神夜大侠7 小时前
VUE 实现公告无缝循环滚动
前端·javascript·vue.js
明辉光焱7 小时前
【Electron】Electron Forge如何支持Element plus?
前端·javascript·vue.js·electron·node.js