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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值