首頁 > 軟體

2023年了該瞭解下WebComponent使用教學

2023-02-25 06:01:38

正文

WebComponent 是官方定義的自定義元件實現方式,它可以讓開發者不依賴任何第三方框架(如Vue,React)來實現自定義頁面元件;達到元件複用效果

一個簡單例子,讓頁面顯示 hello world:

<body>
  <!-- 使用元件的方式 -->
  <my-text />
  <script>
    class MyText extends HTMLElement {
      constructor() {
        super();
        this.append("hello world");
      }
    }
    window.customElements.define("my-text", MyText);
  </script>
</body>

三項主要技術

1、Custom elements (自定義元素)

  • 一組 JavaScript API,允許您定義 custom elements 及其行為,然後可以在您的使用者介面中按照需要使用它們

分為兩種形式:

自主客製化元素:是獨立的元素,它不繼承其他內建的 HTML 元素,可以直接把它們寫成 HTML 標籤的形式,來在頁面上使用,例如我們剛才自定義的 <my-text>

自定義內建元素:繼承自內建的 HTML 元素。指定所需擴充套件的元素

  • 使用時需通過 is 屬性指定 custom element 的名稱,必須包含一個短橫線
  • 註冊的時候必須使用 extends 的屬性
<!-- 自定義內建元素 使用 is-->
<body>
  <!-- 使用元件的方式 -->
  <p is="color-p" color="green">雲牧</p>
  <script>
    class ColorP extends HTMLParagraphElement {
      constructor() {
        super();
        this.style.color = this.getAttribute("color");
      }
    }
    window.customElements.define("color-p", ColorP, { extends: "p" });
  </script>
</body>

推薦在 connectedCallback 生命週期函數,處理節點操作

<!-- 自主客製化元素-->
<body>
  <my-text />
  <script>
    class MyText extends HTMLElement {
      constructor() {
        super();
      }
      connectedCallback() {
        this.append("hello world");
      }
    }
    window.customElements.define("my-text", MyText);
  </script>
</body>

生命週期函數

connectedCallback:插入檔案時,可能被多次觸發,比如刪除後又新增到檔案

disconnectedCallback:從檔案刪除時,可設定做清理工作

adoptedCallback:被移動新檔案時

attributeChangedCallback:屬性變化時

  • 配合 observedAttributess 屬性一起使用,指定監聽的屬性
  • 使用 setAttribute 方法更新屬性

不同操作觸發的生命週期函數:

例子:

<body>
  <div id="container">
    <p is="my-text" text="雲牧" id="myText"></p>
  </div>
  <button id="btnUpdateText">更新屬性</button>
  <button id="btnRemove">刪除節點</button>
  <button id="btnRestore">恢復節點</button>
  <button id="btnAdopt">移動節點</button>
  <iframe src="./ifr.html" id="ifr"></iframe>
  <script>
    class MyText extends HTMLParagraphElement {
      constructor() {
        super();
      }
      connectedCallback() {
        console.log("生命週期:connectedCallback");
        this.append("你好:" + this.getAttribute("text"));
      }
      disconnectedCallback() {
        console.log("生命週期:disconnectedCallback");
        this.innerHTML = "";
      }
      // 監測的屬性
      static get observedAttributes() {
        return ["text"];
      }
      attributeChangedCallback(name, oldValue, newValue) {
        console.log("生命週期:attributeChangedCallback", name, oldValue, newValue);
        // 最先觸發是此函數,判斷是不是第一次觸發,第一次的話,只由 connectedCallback 處理
        if (oldValue != null) {
          this.replaceChildren("你好:" + newValue);
        }
      }
      adoptedCallback() {
        console.log("生命週期:adoptedCallback");
      }
    }
    window.customElements.define("my-text", MyText, { extends: "p" });
    const myText = document.getElementById("myText");
    btnUpdateText.addEventListener("click", function (e) {
      myText.setAttribute("text", "黛玉");
    });
    btnRemove.addEventListener("click", function (e) {
      myText.remove();
    });
    btnRestore.addEventListener("click", function (e) {
      container.appendChild(myText);
    });
    btnAdopt.addEventListener("click", () => {
      const textNode = ifr.contentWindow.document.getElementById("myText");
      container.appendChild(document.adoptNode(textNode));
    });
  </script>
</body>

2、HTML templates(HTML 模板)

  • 使用 JS 模板字串符的方式建立模板,提示不友好,複用性差
<body>
  <product-item
    name="關東煮"
    img="//img10.360buyimg.com/seckillcms/s200x200_jfs/t1/121953/18/20515/175357/61e7dc79Ee0acbf20/4f4f56abd2ea2f75.jpg!cc_200x200.webp"
    price="49.8"
  ></product-item>
  <script>
    class ProductItem extends HTMLElement {
      constructor() {
        super();
      }
      connectedCallback() {
        const content = `
                  <img class="img" src="https://misc.360buyimg.com/lib/skin/e/i/error-jd.gif" />
                  <div class="name"></div>
                  <div class="price"></div>
              `;
        this.innerHTML = content;
        this.querySelector(".img").src = this.getAttribute("img");
        this.querySelector(".name").innerText = this.getAttribute("name");
        this.querySelector(".price").innerText = this.getAttribute("price");
      }
    }
    window.customElements.define("product-item", ProductItem);
  </script>
</body>

template 方式

<body>
  <!-- template -->
  <template id="tpl-product-item">
    <img class="img" src="https://misc.360buyimg.com/lib/skin/e/i/error-jd.gif" />
    <div class="name"></div>
    <div class="price"></div>
  </template>
  <product-item
    name="關東煮"
    img="//img10.360buyimg.com/seckillcms/s200x200_jfs/t1/121953/18/20515/175357/61e7dc79Ee0acbf20/4f4f56abd2ea2f75.jpg!cc_200x200.webp"
    price="49.8"
  ></product-item>
  <script>
    class ProductItem extends HTMLElement {
      constructor() {
        super();
      }
      connectedCallback() {
        const content = document.getElementById("tpl-product-item").content.cloneNode(true);
        // 插入克隆的模板內容
        this.append(content);
        this.querySelector(".img").src = this.getAttribute("img");
        this.querySelector(".name").innerText = this.getAttribute("name");
        this.querySelector(".price").innerText = this.getAttribute("price");
      }
    }
    window.customElements.define("product-item", ProductItem);
  </script>
</body>

slot

<body>
  <template id="tpl-test">
    <style>
      .title {
        color: green;
      }
    </style>
    <div class="title">標題</div>
    <slot name="slot-des">預設內容</slot>
  </template>
  <test-item>
    <div slot="slot-des">不是預設內容</div>
  </test-item>
  <script>
    class TestItem extends HTMLElement {
      constructor() {
        super();
      }
      connectedCallback() {
        const content = document.getElementById("tpl-test").content.cloneNode(true);
        const shadow = this.attachShadow({ mode: "open" });
        shadow.append(content);
      }
    }
    window.customElements.define("test-item", TestItem);
  </script>
</body>

3、Shadow DOM(影子 DOM)

影子DOM,其內部樣式不共用

<body>
  <!--  不受外部 .container.container 的顏色影響 -->
  <my-item-s></my-item-s>
  <div class="container">My item</div>
  <style>
    .container.container {
      color: green;
    }
  </style>
  <template id="tpl">
    <style>
      .container {
        color: pink;
      }
    </style>
    <div class="container">My Item</div>
  </template>
  <script>
    class MyItemShadow extends HTMLElement {
      constructor() {
        super();
      }
      connectedCallback() {
        const content = document.getElementById("tpl").content.cloneNode(true);
        const shadow = this.attachShadow({ mode: "open" });
        shadow.append(content);
      }
    }
    window.customElements.define("my-item-s", MyItemShadow);
  </script>
</body>

影子DOM,其內部元素不可以直接被存取到

有一個重要的引數 mode

  • open: shadow root 元素通過 js 從外部存取根節點
  • closed:拒絕 js 從外部存取關閉的 shadow root 節點
<body>
  <template id="tpl">
    <div class="title"></div>
    <div class="des"></div>
  </template>
  <note-item class="note-item" title="標題" des="內容"></note-item>
  <script>
    class NoteItem extends HTMLElement {
      constructor() {
        super();
      }
      connectedCallback() {
        const content = document.getElementById("tpl").content.cloneNode(true);
        const shadow = this.attachShadow({ mode: "open" });
        shadow.append(content);
        // 如果是 open 則可以繼續存取操作內部 dom
        // console.log(document.querySelector(".note-item").shadowRoot.querySelector(".title"));
        shadow.querySelector(".title").textContent = this.getAttribute("title");
        shadow.querySelector(".des").textContent = this.getAttribute("des");
      }
    }
    window.customElements.define("note-item", NoteItem);
  </script>
</body>

引入外部樣式:

<body>
  <template id="tpl">
    <!-- 方式一: -->
    <link rel="stylesheet" href="index.css" rel="external nofollow"  />
    <div>My Item</div>
  </template>
  <my-item></my-item>
  <script>
    class MyItem extends HTMLElement {
      constructor() {
        super();
      }
      connectedCallback() {
        const content = document.getElementById("tpl").content.cloneNode(true);
        const shadow = this.attachShadow({ mode: "open" });
        shadow.append(content);
        // 方式二:
        const linkEl = document.createElement("link");
        linkEl.setAttribute("rel", "stylesheet");
        linkEl.setAttribute("href", "index.css");
        shadow.appendChild(linkEl);
      }
    }
    window.customElements.define("my-item", MyItem);
  </script>
</body>

動態建立 webComponent 元件例子

  • 通過建立 商品 元件,並使得點選能跳轉
<body>
  <div id="product-list" style="display: flex"></div>
  <template id="product-item">
    <style>
      .product-item {
        margin-left: 15px;
        cursor: pointer;
      }
      .img {
        width: 100px;
      }
      .name {
        text-align: center;
      }
      .price {
        color: #999;
        text-align: center;
      }
    </style>
    <div class="product-item">
      <img class="img" src="https://misc.360buyimg.com/lib/skin/e/i/error-jd.gif" />
      <div class="name"></div>
      <div class="price"></div>
    </div>
  </template>
  <script>
    class ProductItemElement extends HTMLElement {
      constructor(props) {
        super(props);
        this.addEventListener("click", () => {
          window.open(`https://item.jd.com/${this.id}.html`);
        });
      }
      connectedCallback() {
        const shadow = this.attachShadow({ mode: "open" });
        const content = document.getElementById("product-item").content.cloneNode(true);
        content.querySelector(".img").src = this.img;
        content.querySelector(".name").innerText = this.name;
        content.querySelector(".price").innerText = this.price;
        shadow.appendChild(content);
      }
    }
    window.customElements.define("product-item", ProductItemElement);
  </script>
  <script>
    const products = [
      {
        name: "關東煮",
        img: "//img10.360buyimg.com/seckillcms/s200x200_jfs/t1/121953/18/20515/175357/61e7dc79Ee0acbf20/4f4f56abd2ea2f75.jpg!cc_200x200.webp",
        id: "10026249568453",
        price: 49.8
      },
      {
        name: "土雞蛋",
        img: "//img11.360buyimg.com/seckillcms/s200x200_jfs/t1/172777/32/27438/130981/61fbd2e0E236000e0/7f5284367e2f5da6.jpg!cc_200x200.webp",
        id: "10024773802639",
        price: 49.8
      },
      {
        name: "東北蜜棗粽子",
        img: "//img20.360buyimg.com/seckillcms/s200x200_jfs/t1/129546/31/19459/110768/60b1f4b4Efd47366c/3a5b80c5193bc6ce.jpg!cc_200x200.webp",
        id: "10035808728318",
        price: 15
      }
    ];
    const productList = document.getElementById("product-list");
    const elList = products.map(product => {
      // 建立元件
      const el = document.createElement("product-item");
      el.img = product.img;
      el.name = product.name;
      el.price = product.price;
      el.id = product.id;
      return el;
    });
    productList.append.apply(productList, elList);
  </script>
</body>

以上就是2023年了該瞭解下WebComponent使用教學的詳細內容,更多關於WebComponent使用教學的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com