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

相关推荐
修己xj36 分钟前
告别手动存图!这款叫 Fatkun 的浏览器插件,简直是素材收集神器
前端
袋鼠云数栈1 小时前
从前端到基础设施,ACOS 如何打通企业全链路可观测
运维·前端·人工智能·数据治理·数据智能
AskHarries1 小时前
系统提示词、开发者指令和用户输入的优先级
java·前端·数据库
Moment2 小时前
长上下文会最终杀死 Rag 吗?
前端·javascript·后端
qcx232 小时前
【系统学AI】25 论文导读 ①:两篇改变 AI 的开山之作——Attention Is All You Need & ReAct
前端·人工智能·react.js·transformer
kyriewen3 小时前
大文件上传最全指南:分片、断点续传、秒传,一篇就够了
前端·javascript·面试
我叫黑大帅4 小时前
解决聊天页内部滚轮改为页面滚动问题
javascript·后端·面试
郑洁文4 小时前
基于Python的Web命令执行漏洞自动化检测系统
前端·python·网络安全·自动化
新酱爱学习4 小时前
手搓 10 个 Skill 后,我把重复劳动收敛成了一套零依赖 CLI 工具
前端·javascript·人工智能
罗超驿4 小时前
13.JavaScript 新手入门指南:语法、变量、流程控制全解析
开发语言·javascript