初識web-components 而且快速實現todolist

在當下,前端三巨頭vue react ng都是提倡組件化開發的,在原生領域,web-components也逐漸成爲標準。近段時間大熱的omi就是基於web-components實現的javascript

本文github地址css

web-components主要由3部分組成html

  • custom-elements
  • shadow-dom
  • slot template

custom-elements

從字面意思能夠知道這是自定義元素的意思。區別於原生html元素,咱們能夠本身定義它的行爲。按照是否從原生html元素繼承,可分下面兩類前端

兩類custom元素
  • Autonomous custom elements。徹底自定義元素
  • Customized built-in elements .從常規html元素繼承的
生命週期

custom-elements 比較讚的一點是具備如下的生命週期vue

  • connectedCallback 鏈接到dom後觸發 相似於react的componentDidMount,當自定義元素首次加載到dom會觸發,若是咱們想獲取傳入的attributes來選擇展現內容的話,須要將邏輯放在這個週期內而不是constructor中,constructor是取不到attributes的值,還須要注意的是,受html限制,經過html傳入的attributes值都是字符串java

  • disconnectedCallback 當自定義元素從DOM樹中脫離觸發 對於綁定元素的事件監聽,能夠在這裏進行解綁,防止內存泄漏react

  • adoptedCallback 當自定義元素移動到新的document觸發git

  • attributeChangedCallback 自定義元素屬性值改變時觸發。這個須要配合static get observedAttributes(){return ['須要監聽的屬性']}使用,表示哪些屬性變化纔會觸發這個生命週期。對於動態attributes進行渲染,這個很是好用github

一個Autonomous custom elements web-components一般使用方法以下web

class App extends HTMLElement {
  static get observedAttributes() {
    return ['text'];
  }

  constructor() {
    super();
    // 在constructor中初始化
// 建立一個shadow元素,會css隔離的,一些原生html元素例如video等也是基於shadowdom實現的
    const shadow = this.attachShadow({mode: 'open'});

    const div = document.createElement('div');
    
    // web-components內的樣式,外部不影響
    const style = document.createElement('style');
    
    shadow.appendChild(style);
    shadow.appendChild(div);
  }

  connectedCallback() {}

  disconnectedCallback() {}

  adoptedCallback() {}

  attributeChangedCallback(name, oldValue, newValue) {}
}

customElements.define('my-app', App);
複製代碼

若是是擴展原生元素的web-components則是相似

class CustomP extends HTMLParagraphElement {
 ...
}
customElements.define('custom-p', CustomP,{extend:'p'});
複製代碼

shadom-dom

shadom-dom操做和日常的dong操做差很少,對this.attachShadow({mode: 'open'});。shadow-dom最大的好處就是實現了dom隔離。例如css只會對內部的shadow-dom有效,並不影響外部的元素。這應該是css最完美的解決方案了,目前不少組件化css解決方案css modules、各類css in js都不太優雅

// this是custom-element
    const shadow = this.attachShadow({mode: 'open'});

    const div = document.createElement('div');
    
    const style = document.createElement('style');
    
    shadow.appendChild(style);
    shadow.appendChild(div);
複製代碼

template 和 slot

相似於vue的概念,用來實現html複用和插槽效果

template結合custom-elements用法
<template id="my-paragraph">
 <style> p { color: white; background-color: #666; padding: 5px; } </style>
 <p>My paragraph</p>
</template>
複製代碼
// mdn例子
customElements.define('my-paragraph',
 class extends HTMLElement {
   constructor() {
     super();
     let template = document.getElementById('my-paragraph');
     let templateContent = template.content;

     const shadowRoot = this.attachShadow({mode: 'open'})
       .appendChild(templateContent.cloneNode(true));
 }
})
複製代碼
slot用法則和vue的基本一致

使用

web-components的使用很是方便,有幾種方法 一、直接html中使用自定義標籤

<custom-element></custom-element>
複製代碼

二、經過js引入

const CustomElement = customElements.get('custom-element');
const customElement = new CustomElement();
// or
document.createElement('custom-elemen')

// append進dom
複製代碼

實際開發結合polymer體驗更佳

最後寫了個web-compoennts todolist

demo

代碼以下

// TodoList.js
class TodoList extends HTMLElement {
  constructor() {
    super();
    this.shadowdom = this.attachShadow({ mode: "open" });
    this.handleRemove = this.handleRemove.bind(this);
  }

  get data() {
    const dataAttribute = this.getAttribute("data");
    if (dataAttribute) {
      return Array.isArray(dataAttribute)
        ? dataAttribute
        : JSON.parse(dataAttribute);
    } else {
      return [];
    }
  }

  set data(val) {
    this.setAttribute("data", JSON.stringify(val));
    this.render();
  }

  handleRemove(e) {
    this.remove(e.detail.index);
  }

  connectedCallback() {
    this.render();
    this.shadowdom.addEventListener("sub", this.handleRemove);
  }
  disconnectedCallback() {
    this.shadowdom.removeEventListener("sub", this.handleRemove);
  }
  //渲染內容
  render() {
    // 簡便起見,每次渲染前先清空shadowdom的內容
    let last = null;
    while ((last = this.shadowdom.lastChild)) {
      this.shadowdom.removeChild(last);
    }
    this.data.forEach((item, index) => {
      const todoiterm = new (customElements.get("todo-iterm"))();
      todoiterm.innerHTML = `<span slot='text'>${item}</span>`;
      todoiterm.setAttribute("data-index", index);

      this.shadowdom.appendChild(todoiterm);
    });
  }

  addIterm(text) {
    this.data = [...this.data, text];
  }
  remove(deleteIndex) {
    this.data = this.data.filter((item, index) => index != deleteIndex);
  }
}
customElements.define("todo-list", TodoList);
複製代碼
// TodoIterm.js
class TodoIterm extends HTMLElement {
  constructor() {
    super();
    const template = document.getElementById("list-item");
    const templateContent = template.content;
    const shadowdom = this.attachShadow({ mode: "open" });

    shadowdom.appendChild(templateContent.cloneNode(true));
    shadowdom.getElementById("sub").onclick = e => {
      const event = new CustomEvent("sub", {
        bubbles: true,
        detail: { index: this.dataset.index},
      });
      this.dispatchEvent(event)
    };
  }
}
customElements.define("todo-iterm", TodoIterm);
複製代碼
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>web-components</title>
    <script src="./TodoList.js"></script>
    <script src="./TodoIterm.js"></script>
  </head>
  <body>
    <template id="list-item">
      <style> * { color: red; } </style>
      <li><slot name="text">nothing write</slot><button id="sub">-</button></li>
    </template>
    <!-- <todo-list></todo-list> -->
    <div>
        <input id='input'/>
        <button id='add'>+</button>
    </div>
    <script> // 加載web compoennts const List = customElements.get('todo-list'); const todoList = new List() document.body.appendChild(todoList) document.getElementById('add').onclick = function(){ const value = document.getElementById('input').value todoList.addIterm(value) } </script>
  </body>
</html>

複製代碼

一些須要注意的地方: 一、經過html傳遞屬性值,因爲是經過attributes傳入,因此都是字符串 二、組件之間的通訊傳遞須要經過自定義事件

相關文章
相關標籤/搜索