先上图看效果,再说原理

user-card就是一个原生的组件的实列,
1,支持传入图像、姓名等props,prop更新时自动更新视图
2,支持使用slot,插入html片段,使用同vue类似
3,支持使用template标签,写组件模板
4,支持调用组件的方法,也可以传入方法给组件调用
5,兼容性良好,纯原生,主流浏览器都兼容
在这里插入图片描述

概念

    组件是前端的发展方向,现在流行的 React 和 Vue 都是组件框架。
    谷歌公司由于掌握了 Chrome 浏览器,一直在推动浏览器的原生组件,即 Web Components API。
    相比第三方框架,原生组件简单直接,符合直觉,不用加载任何外部模块,代码量小。目前,它还在不断发展,但已经可用于生产环境。

使用场景

    个人认为他当前可以承担的的角色:公共组件。
    在不同框架(vue,react,angular等等),写的项目中,用同一组件实现同一功能。

核心API

1,自定义元素

customElements.define(tag-name,tagClass,{extends:tagname})
第一个参数:标签名,用 - 横线连接小写子母
第二个参数:标签的类
eg:

class MyButton extends HTMLElement {
                constructor() {
                    super();
                    this.style.cssText = `display:block;background:red;width:200px;height:30px;line-height:30px;text-align:center;`;
                    this.addEventListener("click", () => alert("我是自定义的按钮"));
                    this.append("自定义按钮");
                }
            }
            window.customElements.define("my-button", MyButton);```

使用:

```xml
    <my-button>666 </my-button>

第三个参数:是继承的标签名(会获取标签的基础样式等),使用时用is 指定是哪个自定义元素。

 window.customElements.define("my-button2", MyButton2, {
                extends: "button",
            });

使用:

    <button is="my-button2">extends Button</button>

2,template

template是一个原生的标签,他的内容不会渲染在页面上

有了template, 就不需要用js来创建dom,结构了

    <template id="userCardTemplate">
      <img src="./img/1.webp" class="image" />
      <div class="container">
        <p class="name">李四</p>
        <p class="email">lisil@some-email.com</p>
        <button class="button">Follow</button>
      </div>
      <style>
        user-card {
          display: flex;
          align-items: center;
          width: 450px;
          height: 180px;
          background-color: #d4d4d4;
          border: 1px solid #d5d5d5;
          box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
          border-radius: 3px;
          overflow: hidden;
          padding: 10px;
          box-sizing: border-box;
          font-family: "Poppins", sans-serif;
        }
        .image {
          width: 200px;
          margin-right: 20px;
        }
      </style>
    </template>

js:

class UserCard extends HTMLElement {
 constructor() {
            super();
            var templateElem = document.getElementById("userCardTemplate");
            console.log(templateElem.constructor.name); //HTMLTemplateElement
            var content = templateElem.content.cloneNode(true);
            this.appendChild(content);
          }
        }
        window.customElements.define("user-card", UserCard);

3,组件的传参和生命周期钩子

1, 传参同原生
2,生命周期
connectedCallback:当 custom element首次被插入文档DOM时,被调用。

disconnectedCallback:当 custom element从文档DOM中删除时,被调用。

adoptedCallback:当 custom element被移动到新的文档时,被调用。

attributeChangedCallback: 当 customelement增加、删除、修改自身属性时,被调用。
observedAttributes:监视属性的更新,类似useEffect,的第二个参数

        class UserCard3 extends HTMLElement {
          //
          //    useEffect(() => {
          //   if (appDataUrl) {
          //     findCacheVideo();
          //   }
          // }, [appDataUrl]);
          static get observedAttributes() {
            return ["img", "name", "email", "buttonclick"];
          }
          constructor() {
            super();
            var templateElem = document.getElementById("userCardTemplate");
            var content = templateElem.content.cloneNode(true);
            this.appendChild(content);
          }
          // 插入dom时
          connectedCallback() {
            this.querySelector(".input").focus();
          }
          // 属性更新、删除、增加; 首次插入dom也会触发
          attributeChangedCallback(name, oldValue, newValue) {
            console.log(name, oldValue, newValue);
            this.updateAttr(name, newValue);
          }
          updateAttr(name, value) {
          // 此处投机取巧
            var el = this.querySelector(`.${name}`);
            if (name === "img") {
              el.setAttribute("src", value);
            } else if (name === "buttonclick") {
              el.onclick = new Function(value);
            } else {
              el.innerText = value;
            }
          }
          // 提供api把内部的值传递出去
          getInputValue() {
            return this.querySelector(".input").value;
          }
        }
        window.customElements.define("user-card3", UserCard3);

4,shadow DOM

Web components的一个重要属性是封装——可以将标记结构、样式和行为隐藏起来,并与页面上的其他代码相隔离,保证不同的部分不会混在一起,可使代码更加干净、整洁。其中,ShadowDOM 接口是关键所在,它可以将一个隐藏的、独立的 DOM附加到一个元素上。

主要作用隔离,防止受到外部样式,js的影响

改写:

        class UserCard3 extends HTMLElement {
          static get observedAttributes() {
            return ["img", "name", "email", "buttonclick"];
          }
          constructor() {
            super();
            var templateElem = document.getElementById("userCardTemplate");
            var content = templateElem.content.cloneNode(true);
            // 创建影子dom
            this.shadowDOM = this.attachShadow({ mode: "closed" });
            this.shadowDOM.appendChild(content);
          }
          // 插入dom时
          connectedCallback() {
            this.shadowDOM.querySelector(".input").focus();
          }
          // 属性更新、删除、增加; 首次插入dom也会触发
          attributeChangedCallback(name, oldValue, newValue) {
            // console.log(name, oldValue, newValue,this);
            this.updateAttr(name, newValue);
          }
          updateAttr(name, value) {
            var el = this.shadowDOM.querySelector(`.${name}`);
            if (name === "img") {
              el.setAttribute("src", value);
            } else if (name === "buttonclick") {
              el.onclick = new Function(value);
            } else {
              el.innerText = value;
            }
          }
          // 提供api把内部的值传递出去
          getInputValue() {
            return this.shadowDOM.querySelector(".input").value;
          }
        }
        window.customElements.define("user-card3", UserCard3);

5,slot

  slot是一个原生的标签,本身不会被渲染在页面上,但是内容会 
  vue的slot模仿了原生slot的基本使用方式, 所以slot的使用就是你想象的那样
    <template id="userCardTemplate">
      <img src="./img/2.webp" class="img" />
      <div class="container">
        <p class="name">姓名</p>
        <p class="email">邮箱</p>
        <button class="buttonclick">Follow</button>
        <input type="text" class="input" value="1" />
      </div>
      <slot></slot>
      <slot name="likes">我的爱好是:敲代码</slot>
      <slot name="eat">我喜欢吃:肉肉</slot>
      <style>
        * {
          box-sizing: border-box;
        }
        :host {
          display: flex;
          align-items: center;
          width: 650px;
          height: 280px;
          background: #d4d4d4 !important;
          border: 1px solid #d5d5d5;
          box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
          border-radius: 3px;
          overflow: hidden;
          padding: 10px;
          box-sizing: border-box;
          font-family: "Poppins", sans-serif;
          /* border: 1px solid red; */
        }
        .img {
          width: 200px;
          margin-right: 20px;
        }
      </style>
    </template>

封装和源码

1,user-card封装

userCard.js
避免页面写模板,写好后,复制采用字符串的形式

window.addEventListener("load", () => {
  class UserCard extends HTMLElement {
    static get observedAttributes() {
      return ["img", "name", "email", "buttonclick"];
    }
    constructor() {
      super();
      const template = document.createElement("template");
      template.innerHTML = `
        <img src="./img/2.webp" class="img" />
        <div class="container">
          <p class="name">姓名</p>
          <p class="email">邮箱</p>
          <button class="buttonclick">Follow</button>
          <input type="text" class="input" value="1" />
        </div>
          <slot name="likes">我的爱好是:敲代码</slot>
          <slot name="eat">我喜欢吃:肉肉</slot>
        <style>
        :host {
          display: flex;
          align-items: center;
          width: 650px;
          height: 280px;
          background: #d4d4d4!important;
          border: 1px solid #d5d5d5;
          box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
          border-radius: 3px;
          overflow: hidden;
          padding: 10px;
          box-sizing: border-box;
          font-family: "Poppins", sans-serif;
        }
        .img {
          width: 200px;
          margin-right: 20px;
        }
        </style>
       `;
      // 创建影子dom
      this.shadowDOM = this.attachShadow({ mode: "closed" });
      
      this.shadowDOM.appendChild(template.content.cloneNode(true));
    }
    // 插入dom时
    connectedCallback() {
      this.shadowDOM.querySelector(".input").focus();
    }
    // 属性更新、删除、增加; 首次插入dom也会触发
    attributeChangedCallback(name, oldValue, newValue) {
      // console.log(name, oldValue, newValue);
      this.updateAttr(name, newValue);
    }
    updateAttr(name, value) {
      var el = this.shadowDOM.querySelector(`.${name}`);
      if (name === "img") {
        el.setAttribute("src", value);
      } else if (name === "buttonclick") {
        el.onclick = new Function(value);
      } else {
        el.innerText = value;
      }
    }
    // 提供api把内部的值传递出去
    getInputValue() {
      return this.shadowDOM.querySelector(".input").value;
    }
  }
  window.customElements.define("user-card", UserCard);
});

2,应用源码

<!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>简单的封装一下</title>
    <script src="./src/userCard.js"></script>
  </head>
  <body>
    <user-card
      id="user-card3"
      img="./img/2.webp"
      name="王五3"
      email="110@qq.com"
      buttonclick="test()"
    >
      <div slot="likes" class="likes">
        <h3>我的爱好有很多:</h3>
        <p>🏀</p>
        <p>🎣</p>
        <p>🏸</p>
      </div>
      <div slot="eat" class="eat">
        <h3>我爱吃的很多:</h3>
        <p>🐟</p>
        <p>🍚</p>
        <p>🦆</p>
      </div>
    </user-card>
    <br />
    <button onclick="getUserCardInputValue()">
      读取user-card3的输入框的value
    </button>
    <br />
    <script>
      window.addEventListener("load", () => {
        var card3 = document.getElementById("user-card3");
        setTimeout(() => {
          card3.setAttribute("img", "./img/3.webp");
          card3.setAttribute("name", "赵四");
          // test没有observed 所有不会触发attributeChangedCallback钩子
          card3.setAttribute("test", "test");
        }, 2000);
      });

      // 读取组件内部的值
      function getUserCardInputValue() {
        var card3 = document.getElementById("user-card3");
        alert(card3.getInputValue());
      }

      // 给组件传递一个方法
      function test() {
        alert("test函数");
      }
    </script>
  </body>
</html>

组件库

ivyUI
xyUI

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐