如何创建WebComponent?这题应该要会

前言

在前端框架百花齐放的今天,因为各项目使用不同框架导致需要重复进行UI组件架开发成为一个老大难,举个栗子:

打蛋同学需要开发一个人名片,但是他pc端使用的是Vue、移动端使用的是React,那么打蛋就需要开发一个vue卡片组件、React卡片组件,聪明的打蛋会想能不能开发一个跨框架的web组件呢(其实是希望React和Vue能不能来个世纪和解,在React里面支持使用Vue组件😁),那这个时候就请出WebComponent了。

什么是WebComponent❓

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

简单点来说就是WebComponent 可以跨框架使用,并且开发者可以对其进行自定义扩展封装来达到UI组件复用目的,这是一个web标准,是原生浏览器支持的,与框架无关

如何实现WebComponent❓

这里有三个关键技术

  1. Custom element(自定义元素):一组 JavaScript API,允许你定义 custom elements 及其行为,然后可以在你的用户界面中按照需要使用它们,简单点来说就是允许开发者自定义注册一个web组件。
  2. Shadow DOM(影子 DOM ):一组 JavaScript API,用于将封装的"影子"DOM 树附加到元素(与主文档 DOM 分开呈现)并控制其关联的功能。通过这种方式,你可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突,总结就是使用了Shadow Dom 可以把组件的样式和行为封装在一个独立的容器内,不会影响外部的样式和行为,相当于沙箱隔离。
  3. HTML template(HTML 模板)<template><slot>元素使你可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用,简单来说就是这两个标签在html中使用,里面的内容不会展示出来,并且他们可以被扩展使用

简单创建一个WebComponent

vCustomButton.js 复制代码
//定义一个类继承自HTMLElement
class VCustomButton extends HTMLElement {
    constructor() {
        super();
        //先获取目标节点 
        const template = document.getElementById('v-custom-button'),
        // shadow Dom 这里主要是将样式和行为隔离 使得webComponent的样式以及行为都在一个隔离的容器 不会影响外部样式和行为
        // 这一点很像vue 的 scoped 但vue采用的是往节点添加data-xxx属性来区别
        // mode决定外部是可以访问该根节点 open是可以 closed是拒绝访问
            rootDom = this.attachShadow({ mode: 'open'})
        //复制模板
        const content = template.content.cloneNode(true)
        //将模板内容添加到ShadowDOM中
        rootDom.appendChild(content);
    }
} 
//这里是进行注册
window.customElements.define('v-custom-button', VCustomButton)

使用组件,注意这里不可以直接打开改本地html,file://访问js会报错,因为file是伪协议,在浏览器下会报跨域错误,这里我使用的的live-server(vscode插件、npm包下载都可)起了本地服务

index.html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>使用webComponent</title>
    <script type="module" src="./v-custom-button.js"></script>
</head>
<body>
    <!-- 解析器不会将这个模板解析出来 -->
    <template id="v-custom-button">
        <style>
            .v-button {
                display: inline-block;
                -webkit-appearance: none;
                box-sizing: border-box;
                box-sizing: content-box;
                outline-style: none;
                padding: 8px;
                background-color: #fff;
                border-radius: 4px;
                border: 1px solid #467689;
                cursor: pointer;
            }
        </style>
        <button class="v-button">这是webComponent按钮</button>
    </template>
    <v-custom-button id="v-custom-button"/>
</body>
</html>

查看效果,完美🤩

但是我还想自定义传入props、事件行为,又该怎么做呢?

传入props比较简单,只需要新增对应属性名即可

index.html 复制代码
<v-custom-button id="v-custom-button" buttonText="我在自定义按钮名称"/>

在class构造器里面进行赋值

js 复制代码
...
        const content = template.content.cloneNode(true),
                title = content.querySelector('.v-button'); //获取节点
        
        title.innerText = this.getAttribute('buttonText') //赋值
...

来看看效果

也可以使用WebComponent的生命周期回调attributeChangedCallback,这里还要配合observedAttributes一起使用

  1. observedAttributes,返回需要被监听的属性,返回值是一个数组
  2. attributeChangedCallback,自定义元素的一个属性被增加、移除或更改时被调用,注意这里的属性必须贝上文observedAttributes的返回值包含
js 复制代码
...
constructor() { ... }

static get observedAttributes() {
  return ['button-text']
}

attributeChangedCallback() {
  console.log('触发属性更新了');
  const text = this.getAttribute('button-text')
  this.$button.innerText = text
}

这里我们看到控制台attributeChangedCallback被执行了

实现下数据驱动视图

这里我们要实现下点击按钮触发按钮文本更新,很简单,这里我们对shadowDom进行点击回调监听,修改buttonText这个属性即可

js 复制代码
...
constructor(){
    ...
    this.shadowRoot.querySelector('.v-button').addEventListener('click', (e) =>  {
      this.setAttribute('button-text', '我被点击修改了')
    })
}

这里我们也可以看到,attributeChangedCallback再次被触发

支持插槽

这里的用法也比较简单,只需要在模板加入 <slot>

index.html 复制代码
<template id="v-custom-button">
   <style>
      .v-button {
        display: inline-block;
        -webkit-appearance: none;
        box-sizing: border-box;
        box-sizing: content-box;
        outline-style: none;
        padding: 8px;
        background-color: #fff;
        border-radius: 4px;
        border: 1px solid #467689;
        cursor: pointer;
      }
   </style>
   <slot></slot>
   <button class="v-button">这是webComponent按钮</button>
</template>

在加载卸载钩子里面执行监听

当组件被加载时候,我们执行onclick监听,当组件被卸载,我们也需要移除监听,webComponent也提供了两个生命周期钩子:

  1. connectedCallback:当自定义元素第一次被连接到文档 DOM 时被调用,就是被挂载时候使用。
  2. disconnectedCallback:当自定义元素与文档 DOM 断开连接时被调用,就是组件被卸载时候调用。
js 复制代码
...
    connectedCallback() {
        console.log('组件被挂载');
        this.shadowRoot.querySelector('.v-button').addEventListener('click', (e) =>  {
            this.setAttribute('button-text', '我被点击修改了')
        })
    }
    
    disconnectedCallback() {
        console.log('组件被卸载载');
        this.shadowRoot.querySelector('.v-button').removeEventListener('click')
    }
...

让我们来看看,生命周期函数被触发了

结语

从2011年WebComponent概念被初次提及到2012年被实现(也就是template、shadowDom),算起来也有一个轮回了,目前主流的WebComponent框架有谷歌的polymer、腾讯omi,京东的微前端框架mirco-app也是类WebComponent实现的,相信随着微前端框架的流行WebComponent会成为一个趋势,接下来我补全一下代码:

index.html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>使用webComponent</title>
    <script type="module" src="./v-custom-button.js"></script>
</head>
<body>
    <!-- 解析器不会将这个模板解析出来 -->
    <template id="v-custom-button">
        <style>
            /* 样式支持定义 类似于html: root  可以进行变量声明*/
            :host {
                --button-bg-color: #fff;
                --button-border: 1px solid #467689;
            }
            .v-button {
                display: inline-block;
                -webkit-appearance: none;
                box-sizing: border-box;
                box-sizing: content-box;
                outline-style: none;
                padding: 8px;
                background-color: #fff;
                border-radius: 4px;
                border: var(--button-border);
                cursor: pointer;
            }
        </style>
        <slot></slot>
        <button class="v-button">这是webComponent按钮</button>
    </template>
    <v-custom-button id="v-custom-button" button-text="我在自定义按钮名称">
        <span>哈哈哈</span>
    </v-custom-button>
</body>
</html>
VCustomButton.js 复制代码
//定义一个类继承自HTMLElement
class VCustomButton extends HTMLElement {
    constructor() {
        super();
        //先获取目标节点 
        const template = document.getElementById('v-custom-button'),
        // shadow Dom 这里主要是将样式和行为隔离 使得webComponent的样式以及行为都在一个隔离的容器 不会影响尾外部样式
        // 这一点很像vue 的 scoped 但vue采用的是往节点添加data-xxx属性来区别
        // mode决定外部是可以访问根节点 open是可以 closed是拒绝访问n
            shadowRootDom = this.attachShadow({ mode: 'open'});
        //复制模板
        const content = template.content.cloneNode(true),
                button = content.querySelector('.v-button');
        this.$button = button
        // const text = this.getAttribute('button-text')
        // button.innerText = text
        shadowRootDom.appendChild(content);

        
    }

    static get observedAttributes() {
        return ['button-text']
    }

    attributeChangedCallback() {
        console.log('触发属性更新了');
        const text = this.getAttribute('button-text')
        this.$button.innerText = text
    }

    connectedCallback() {
        console.log('组件被挂载');
        this.shadowRoot.querySelector('.v-button').addEventListener('click', (e) =>  {
            this.setAttribute('button-text', '我被点击修改了')
        })
    }
    
    disconnectedCallback() {
        console.log('组件被卸载载');
        this.shadowRoot.querySelector('.v-button').removeEventListener('click')
    }
    
} 
window.customElements.define('v-custom-button', VCustomButton)
相关推荐
清灵xmf2 分钟前
TypeScript 类型进阶指南
javascript·typescript·泛型·t·infer
小白学大数据8 分钟前
JavaScript重定向对网络爬虫的影响及处理
开发语言·javascript·数据库·爬虫
qq_3901617717 分钟前
防抖函数--应用场景及示例
前端·javascript
334554321 小时前
element动态表头合并表格
开发语言·javascript·ecmascript
John.liu_Test1 小时前
js下载excel示例demo
前端·javascript·excel
Yaml41 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
PleaSure乐事1 小时前
【React.js】AntDesignPro左侧菜单栏栏目名称不显示的解决方案
前端·javascript·react.js·前端框架·webstorm·antdesignpro
哟哟耶耶1 小时前
js-将JavaScript对象或值转换为JSON字符串 JSON.stringify(this.SelectDataListCourse)
前端·javascript·json
getaxiosluo1 小时前
react jsx基本语法,脚手架,父子传参,refs等详解
前端·vue.js·react.js·前端框架·hook·jsx
理想不理想v1 小时前
vue种ref跟reactive的区别?
前端·javascript·vue.js·webpack·前端框架·node.js·ecmascript