【前端笔记】WebComponent 知识点个人总结

自从大二初次接触前端以来,一直都有记markdown笔记的习惯.又因为掘金最近有一个活动.所以开了这个专栏。我会写一些业务相关的小技巧和前端知识中的重点内容之类的整理一下发上来。难免有点地方不够细致。欢迎大家指教

这篇文章 是我 学习 webcomponet 组件至今的一些 知识点,包括生命周期,属性传递,事件触发,shadow选择器之类。下面的两张截图是 我开发的 基于webcomponent 的 组件库。

代码:github.com/yilaikesi/w...

代码:github.com/yilaikesi/w...

有空的朋友可以给我的代码点点star之类的嘿嘿。

废话少说,我们直接开始吧。

1.0 web component

1.0.0 最基本的webcomponent

typescript 复制代码
// removeNode(button);
​
import styles from "../css/normal.css" assert {type : 'css'};
class myDiv extends HTMLElement {
    // 监听
    static get observedAttributes(){
        return ["option","name"]
    }
    constructor() {
        super();
        this.shadowRoot!
        // 这样我们才能够去追加元素
        this.attachShadow({ mode: 'open' })
      
    }
​
    // 重要:生命周期方法 开始
    connectedCallback() {
        console.log("connectedCallback生命周期")
​
    }
    attributeChangedCallback(attr:string,oldValue:string,newValue:string){
    }
​
    render() {
        let nodeTemplate = document.createElement("template")
        nodeTemplate.innerHTML = `
            <div class="content" >
                <div class="title">组件 </div> 
                <slot name="container"></slot>
            </div>
        `
        this.shadowRoot!.appendChild(nodeTemplate.content)
        this.shadowRoot!.adoptedStyleSheets =[styles]
    }
}
// 名字必须小写 必须有横线
customElements.define("my-div", myDiv)
​
​

1.0.0 生命周期

scss 复制代码
1.shadow dom 
专门操作自定义元素
2.自定义元素
js中 继承 HTMLElement 获得的
有四个生命周期:
connectedCallback() // 挂载的时候
disconnectedCallback() // 卸载的时候
adoptedCallback() // 移动的时候
attributeChangeCallback() // 属性变化时
3.temple
方便定义插槽

1.0.1 helloworld | 生命周期 | shadow dom | host选择器(这个非常重要) | 样式继承

w3c中 有一些 东西可以继承,有一些东西继承不了。如果需要不继承所有属性

可以设置

xml 复制代码
:host {
    all:initial;
}
​
<!-- 什么需要设置全局 就写在:host里面 -->
:host {
   xx
    
}
​
<!-- 属性中包含primary 就会生效 -->
:host([theme~='primary']) {
   
}
xml 复制代码
html 中
  <!-- 重要,最好给自定义元素一个display为block 不然没有宽高 -->
<script src="main.js"></script>
​
<my-div  option="你好">
    啊啊啊
</my-div>
javascript 复制代码
//  1.自定义标签都是用class 的形式去继承
​
class myDiv extends HTMLElement {
    constructor() {
        super();
        // 这样我们才能够去追加元素
        this.attachShadow({ mode: 'open' })
​
    }
​
    // 重要:生命周期方法 开始
    connectedCallback() {
        console.log("connectedCallback生命周期")
​
       this.render({
            option:this.getAttribute("option")
       })
    }
    render(data) {
        let { option } = data
        let nodeTemplate = document.createElement("template")
        nodeTemplate.innerHTML = `
            <div class="content" >
                <div class="title">${option} </div> 
                <slot></slot>
            </div>
        `
        let nodeStyles = document.createElement("style")
        // shadow dom 的样式绝对隔离
        // 重要: :host选择器可以选中根也就是my-div的样式。外面的选择器样式要高于这个
        // :host(.active) .content 这一行代码指的是 这个最外层时 active 样式 ,然后得到其中的 .content 的样式 
        nodeStyles.innerHTML = `
            :host(.active) .content{
                
                margin-top:20px;
                background:rgba(0,0,0,30%);
            }
            :host{
                display:block
            }
            .content{
                width:100px;
                height:100px;
                background:rgba(0,0,0,20%)
            }
           
        `
        this.shadowRoot.appendChild(nodeTemplate.content)
        this.shadowRoot.appendChild(nodeStyles)
    }
}
​
// 名字必须小写 必须有横线
customElements.define("my-div", myDiv)

1.0.2 shadowRoot | 选择shadowdom里面元素

dart 复制代码
 // 重要:选择shadow 的 元素 :二次selector+shadowRoot
// console.log(document.querySelector("my-div").shadowRoot.querySelector(".content"))
​
​
// 重要:向着下面找。调用父方法 selector.方法
// 子方法就 二次selector+shadowRoot.方法
// 往回找 方法 可以 在子元素上 子元素.getRootNode().host.方法. 单单子元素.getRootNode() 那么就是拿到了shadow 元素
console.log(document.querySelector("my-div").borderAdd())
document.querySelector("my-div").setAttribute("option","改编")

1.0.3 调用方法 | 元素.方法

scala 复制代码
class myDiv extends HTMLElement  里面添加这个方法,然后只要选中这个元素就可以调用这个方法  
类似 docuemnt.query("").borderAdd()
类似 docuemnt.query("").shadowdom.querySelector().borderAdd()
​
borderAdd(){
     console.log("borderadd")
     this.shadowRoot.querySelector(".content").style.border="3px solid green"
 }

1.0.4 attributeChangedCallback | observedAttributes| 监听属性变化

scala 复制代码
 class myDiv extends HTMLElement  里面
​
 static get observedAttributes(){
     return ["option"]
 }
​
 
 
attributeChangedCallback(attr,oldValue,newValue){
        
    if(oldValue){
        switch (attr){
            case "option":
                this.shadowRoot.querySelector(".title").textContent = newValue
        }
    }
    console.log("arrributeChangeCallback",attr,oldValue,newValue)
}
​
然后在html上面
 <my-div  option="你好">
​
     啊啊啊
</my-div>

1.0.4 slot 插槽 | ::slotted 选择器 | assignedElements

这玩意外部样式能够影响里面

ini 复制代码
--1.html 调用
<my-div  option="你好">
​
     <div slot="tab">
         tab
    </div>
</my-div>   
​
ini 复制代码
--2.js里面
this.shadowRoot.innerHTML =`
    <div>
            <slot name="tab"></slot>
    </div>
​
`
css 复制代码
--3.如果要选择里面的元素 selectorAll 不起作用
那么 this.tabs = this.shadowRoot.querySelector(`slot[name="tab"]`).assignedElements({flatten:true})
​
 this.tabs = this.shadowRoot.querySelector(`slot[name="tab"]`).assignedElements({flatten:true})
--4. css 选择器可以 像这样 选择
​
 ::slotted([slot="container"]){
     display:none
 }
​
::slotted(.active){
    display:block
}
​
​
​

1.0.5 composedPath | 判断点击位置

javascript 复制代码
document.addEventListener("click",(e)=>{
    // 重要:冒泡的顺序,通过这个可以判断有没有在鼠标内部进行点击
    if(e.composedPath().includes(this)){
        console.log("点击了里面")
    }
})

1.0.6 继承

css 复制代码
一般来说影子属性的值是继承不到的,因此我们可以用
:host{
    --border-color: #6C63FF 
}
--border-color: #6C63FF
然后 var(--border-color)
​
不想基层

1.0.7 :not(:defined) | 防止闪烁

scss 复制代码
 /* 直到影子dom的dom结构被添加之后才会出现,非常有用 */
 :not(:defined){
     display: block;
     opacity: 0;
     transition: all .3s ease;
 }
​
这个选择器确实不得了 js中 居然能够延后执行
setTimeout(()=>{
    this.shadowRoot.appendChild(nodeTemplate.content)
    this.shadowRoot.appendChild(nodeStyles)
},2000)
​
scss 复制代码
完整示例
​
//  1.自定义标签都是用class 的形式去继承
​
class myDiv extends HTMLElement {
    // 监听
    static get observedAttributes(){
        return ["option"]
    }
​
    constructor() {
        super();
        // 这样我们才能够去追加元素
        this.attachShadow({ mode: 'open' })
​
    }
​
    // 重要:生命周期方法 开始
    connectedCallback() {
​
        console.log("connectedCallback生命周期")
​
        // 获取元素
        // console.log(this.shadowRoot.querySelector(".content"))
        // 获取属性
        // console.log( this.getAttribute("data-option"))
       this.render({
            option:this.getAttribute("option")
       })
        document.addEventListener("click",(e)=>{
            // 重要:冒泡的顺序,通过这个可以判断有没有在鼠标内部进行点击
            if(e.composedPath().includes(this)){
                console.log("点击了里面")
            }
        })
    }
​
    // 重要:生命周期方法 重新渲染 .甚至还是第一次进行渲染,比connect还快
    // 会重新渲染 connectCallback
    attributeChangedCallback(attr,oldValue,newValue){
        
        if(oldValue){
            switch (attr){
                case "option":
                    this.shadowRoot.querySelector(".title").textContent = newValue
            }
        }
        console.log("arrributeChangeCallback",attr,oldValue,newValue)
        
    }
​
    borderAdd(){
        console.log("borderadd")
        this.shadowRoot.querySelector(".content").style.border="3px solid green"
    }
​
    render(data) {
        let { option } = data
        
        // console.log()
        let nodeTemplate = document.createElement("template")
        nodeTemplate.innerHTML = `
            <div class="content" >
                <div class="title">${option} </div> 
                <slot name="container"></slot>
            </div>
        `
​
        let nodeStyles = document.createElement("style")
        // shadow dom 的样式绝对隔离
        // 重要: :host选择器可以选中根也就是my-div的样式。外面的选择器样式要高于这个
​
        nodeStyles.innerHTML = `
            :host(.active) .content{
                
                margin-top:20px;
                background:rgba(0,0,0,30%);
            }
            :host{
                display:block
            }
            .content{
                width:100px;
                height:100px;
                background:rgba(0,0,0,20%)
            }
           
            ::slotted([slot="container"]){
                display:none
            }
​
            ::slotted(.active){
                display:block
            }
        `
        
        setTimeout(()=>{
            this.shadowRoot.appendChild(nodeTemplate.content)
            this.shadowRoot.appendChild(nodeStyles)
        },1500)
        
    }
}
​
​
// 名字必须小写 必须有横线
customElements.define("my-div", myDiv)
​
​
​

1.0.8 组件传值

javascript 复制代码
目前最好的方法应该是通过customEvent 来做
​
window.addEventListener("test",(e)=>{
    console.log("出现吧",e.detail)
})
​
let event = new CustomEvent('test',{
    detail:{
        title:"我是标题哦"
    }
})
// 最后还跟着是否能够冒泡和是否阻止默认操作
window.dispatchEvent(
    event,true,false
)
​

1.0.9 组件化css示例

css 复制代码
html 中
<webzen-button theme="primary"></webzen-button>
​
​
~= 的意思是含有
css
:host([theme~='primary']) button{
    color:red
}
:host([theme~='primary'][theme~='small']) button{
    color:red
}

1.0.10 get | set | 修改监听属性值

scala 复制代码
class ElfinTransition extends HTMLElement {
  get name() {
    return this.getAttribute('name');
  }
  set name(value) {
    if (value) {
      this.setAttribute('name', value);
    } else {
      this.removeAttribute('value');
    }
  }
}
getAttribute 和 setAttribute 触发

1.0.12 css modules

python 复制代码
slot.js 中
import styles from "./slot.css" assert {type : 'css'}
this.shadowRoot.adoptedStyleSheets =[styles]
​
​
​

1.0.13 元素动画和元素移除

xml 复制代码
<!DOCTYPE html>
<html lang="en">
​
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
​
<body>
    你好
​
    <script>
        console.log(top)
        let input = document.createElement("input")
        input.setAttribute("type", "text")
        document.body.after(input)
​
​
        function slide(elem, direction) {
            const keyframe = [
                { transform: 'translateX(-50%)', opacity: 0 },
                { transform: 'translateX(0%)', opacity: 1 }
            ]
            return elem.animate(keyframe,{
                fill:"forwards",
                duration:300,
                easing:"ease-in-out"
            })
        }
        let event= slide(input)
​
        // 这里可以移除掉这个元素
        event.onfinish=(e)=>{
            console.log(e,this)
        }
    </script>
</body>
​
</html>

1.0.14 基类

javascript 复制代码
const randomID = () => {
  return Math.random().toString(36).substring(2, 8);
};
​
export default class wz_base extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.id = randomID();
    if (typeof globalThis.components !== 'object') {
      globalThis.components = {};
    }
    Object.defineProperty(globalThis.components, this.id, {
      value: this,
      configurable: true,
    });
  }
​
  disconnectedCallback() {
    delete globalThis.components[this.id];
  }
}
​
javascript 复制代码
使用  
// import events from "inquirer/lib/utils/events";
​
import base from '../base/index.js';
import styles from './index.css' assert { type: 'css' };
​
class TimePicker extends base {
  constructor() {
    super();
  }
  static get observedAttributes() {
    return ["onValue"];
  }
  attributeChangedCallback(name, oldValue, newValue) {
    console.log(name, oldValue, newValue);
    switch (name){
        case "onValue":
            console.log(new Function(newValue))
    }
        
  }
​
  connectedCallback() {
    console.log("组件id:",this.id,globalThis.components[this.id])
    console.log(this.getAttribute("onValue"))
    console.log(new Function(this.getAttribute("onValue"))())
    let nodeTemplate = document.createElement('template');
    nodeTemplate.innerHTML = `
        <label for="time-picker">选择时间:</label>
        <input type="time" id="time-picker">
        `;
    const template = nodeTemplate.content;
    this.shadowRoot.appendChild(template.cloneNode(true));
    this.input = this.shadowRoot.querySelector('#time-picker');
​
    this.input.addEventListener('change', this.handleChange.bind(this));
    //   this.input.addEventListener('change',  eval(this.getAttribute("onValue")(event)));
    this.shadowRoot.adoptedStyleSheets = [styles];
  }
​
  handleChange(event) {
​
    globalThis.eventbus.emit('测试', event.target.value);
    console.log(
        this.getAttribute('onValue')
      );
    this.dispatchEvent(
      new CustomEvent('timeChanged', { detail: event.target.value })
    );
  }
}
​
customElements.define('time-picker', TimePicker);
​

1.0.?? 完整示例

html

xml 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="slot.js"></script>
​
    <style>
        #test{
            background: red;
        }
​
        /* 直到影子dom的dom结构被添加之后才会出现,非常有用 */
        :not(:defined){
            display: block;
            opacity: 0;
            transition: all .3s ease;
        }
     
    </style>
</head>
<body>
    <my-div option="测试">
        <div slot="container" id="test" >
            hello
       </div>
       
       <div slot="container" id="test" class="active">
            tab1
        </div>
​
        <div slot="container" id="test" class="active">
            tab2
        </div>
        <div slot="container" id="test" class="active">
            tab3
        </div>
    </my-div>
​
    <script>
        
​
​
        
​
        customElements.whenDefined('my-div').then(() => {
            console.log("开始define")
        });
        setTimeout(() => {
            console.log(document.querySelector("my-div").shadowRoot.querySelector(`slot[name="container"]`).assignedElements({ flatten: true }))
        }, 3000);
    </script>
</body>
</html>

slot.js

scss 复制代码
//  1.自定义标签都是用class 的形式去继承
​
class myDiv extends HTMLElement {
    // 监听
    static get observedAttributes(){
        return ["option"]
    }
​
    constructor() {
        super();
        // 这样我们才能够去追加元素
        this.attachShadow({ mode: 'open' })
​
    }
​
    // 重要:生命周期方法 开始
    connectedCallback() {
​
        console.log("connectedCallback生命周期")
​
        // 获取元素
        // console.log(this.shadowRoot.querySelector(".content"))
        // 获取属性
        // console.log( this.getAttribute("data-option"))
       this.render({
            option:this.getAttribute("option")
       })
        document.addEventListener("click",(e)=>{
            // 重要:冒泡的顺序,通过这个可以判断有没有在鼠标内部进行点击
            if(e.composedPath().includes(this)){
                console.log("点击了里面")
            }
        })
​
        // this.shadowRoot.querySelector(".content").addEventListener(("click"),()=>{
        //     // window.dispatchEvent
        // })
    }
​
    // 重要:生命周期方法 重新渲染 .甚至还是第一次进行渲染,比connect还快
    // 会重新渲染 connectCallback
    attributeChangedCallback(attr,oldValue,newValue){
        
        if(oldValue){
            switch (attr){
                case "option":
                    this.shadowRoot.querySelector(".title").textContent = newValue
            }
        }
        console.log("arrributeChangeCallback",attr,oldValue,newValue)
        
    }
​
    borderAdd(){
        console.log("borderadd")
        this.shadowRoot.querySelector(".content").style.border="3px solid green"
    }
​
    render(data) {
        let { option } = data
        
        // console.log()
        let nodeTemplate = document.createElement("template")
        nodeTemplate.innerHTML = `
            <div class="content" >
                <div class="title">${option} </div> 
                <slot name="container"></slot>
            </div>
        `
​
        let nodeStyles = document.createElement("style")
        // shadow dom 的样式绝对隔离
        // 重要: :host选择器可以选中根也就是my-div的样式。外面的选择器样式要高于这个
​
        nodeStyles.innerHTML = `
            :host(.active) .content{
                
                margin-top:20px;
                background:rgba(0,0,0,30%);
            }
            :host{
                display:block
            }
            .content{
                width:100px;
                height:100px;
                background:rgba(0,0,0,20%)
            }
           
            ::slotted([slot="container"]){
                display:none
            }
​
            ::slotted(.active){
                display:block
            }
        `
        
        setTimeout(()=>{
            this.shadowRoot.appendChild(nodeTemplate.content)
            this.shadowRoot.appendChild(nodeStyles)
        },1500)
        
    }
}
​
​
// 名字必须小写 必须有横线
customElements.define("my-div", myDiv)
​
​
​

最后的一些絮叨

其实我在公司有一段时间一直在做 webcomponet之类的框架。因为公司业务做的是b端的业务所以一直没有问题。但是c端上面webcompoent 没有 corejs 一样 的 垫片机制。导致很多东西 只能用 react 或者 vue 在包装一层。。。因此目前来说,我们暂时舍弃掉了webcompoent

相关推荐
DT——4 小时前
Vite项目中eslint的简单配置
前端·javascript·代码规范
学习ing小白6 小时前
JavaWeb - 5 - 前端工程化
前端·elementui·vue
真的很上进7 小时前
【Git必看系列】—— Git巨好用的神器之git stash篇
java·前端·javascript·数据结构·git·react.js
胖虎哥er7 小时前
Html&Css 基础总结(基础好了才是最能打的)三
前端·css·html
qq_278063717 小时前
css scrollbar-width: none 隐藏默认滚动条
开发语言·前端·javascript
.ccl7 小时前
web开发 之 HTML、CSS、JavaScript、以及JavaScript的高级框架Vue(学习版2)
前端·javascript·vue.js
小徐不会写代码7 小时前
vue 实现tab菜单切换
前端·javascript·vue.js
2301_765347548 小时前
Vue3 Day7-全局组件、指令以及pinia
前端·javascript·vue.js
喝旺仔la8 小时前
VSCode的使用
java·开发语言·javascript
ch_s_t8 小时前
新峰商城之分类三级联动实现
前端·html