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 个人笔记

相关推荐
musk12125 分钟前
electron 打包太大 试试 tauri , tauri 安装打包demo
前端·electron·tauri
翻滚吧键盘34 分钟前
js代码09
开发语言·javascript·ecmascript
万少1 小时前
第五款 HarmonyOS 上架作品 奇趣故事匣 来了
前端·harmonyos·客户端
OpenGL1 小时前
Android targetSdkVersion升级至35(Android15)相关问题
前端
rzl021 小时前
java web5(黑马)
java·开发语言·前端
Amy.Wang1 小时前
前端如何实现电子签名
前端·javascript·html5
海天胜景1 小时前
vue3 el-table 行筛选 设置为单选
javascript·vue.js·elementui
今天又在摸鱼1 小时前
Vue3-组件化-Vue核心思想之一
前端·javascript·vue.js
蓝婷儿2 小时前
每天一个前端小知识 Day 21 - 浏览器兼容性与 Polyfill 策略
前端
百锦再2 小时前
Vue中对象赋值问题:对象引用被保留,仅部分属性被覆盖
前端·javascript·vue.js·vue·web·reactive·ref