從原生web組件到框架組件源碼(三)

  • 2020 年 11 月 3 日
  • 筆記

快樂的時光都是這麼短暫,轉眼間,web原生組件的知識點已經學完了,這個雖然暫時不一定有用,但是隨着時間的積累,一步一個腳印的積累,你會有相應的收穫,希望能變得更強,比如兩年前我也會想有現成的東西不用,幹嘛要自己寫呢?但是你確定一直用上層的東西,你的收穫有自己寫快嗎? 在開發的過程過能節約下來的時間,我們可以用這個時間拿來學習,這樣隨着時間的積累我們會變得更強,也會慢慢有更多的時間投入生活,進行正向循環

css問題

自定義元素然後是普通的HTML元素,也可以使用css設置樣式

在我們沒有設置shadow DOM的組件,進行全局樣式設置

<app-element></app-element>

<style>
  /* CSS Global */
  app-element {
    display: inline-block;
    padding: 6px 20px;
    background: steelblue;
    color: white;
  }
  app-element span {
    font-weight: bold;
    vertical-align: super;
    font-size: small;
    color: gold;
  }
</style>

<script>
  customElements.define("app-element", class extends HTMLElement {
    connectedCallback() {
      this.innerHTML = `<div class="element">AppElement <span>New!</span></div>`;
    }
  });
</script>
  • 無論文檔是否具有Shadow DOM,都可以從文檔的全局CSS從組件外部)對組件本身進行樣式設置。
  • 只要沒有Shadow DOM可以「保護」組件,就可以對組件中的元素進行全局樣式設置

HTML 外部引入

<app-element></app-element>

<script>
  customElements.define("app-element", class extends HTMLElement {
    connectedCallback() {
      this.innerHTML = `
        <link rel="stylesheet" href="/components/AppElement.css">
		<style>
          @import "/components/AppElement.css";
        </style>
        <div class="element">
          AppElement <span>New!</span>
        </div>
      `;
    }
  });
</script>

程序化動態的方法

<app-element></app-element>

<script>
  import css from "./AppElement.css";

  customElements.define("app-element", class extends HTMLElement {
    connectedCallback() {
      document.adoptedStyleSheets = [...document.adoptedStyleSheets, css];
      this.innerHTML = `
        <div class="element">
          AppElement <span>New!</span>
        </div>
      `;
    }
  });
</script>
this.shadowRoot.adoptedStyleSheets = [...document.adoptedStyleSheets, css];

我們通過import 加載css,在這種情況下,它是組件的相對路徑,在加載css內容中並生成一個對象cssStyleSheet ,然後通過.adoptedStyleSheets 導入css

這種方法直接使用是錯誤的,需要引入插件css-loader,應該要藉助webpack ,原生不能直接使用

import css from "./AppElement.css"

css自定義屬性

var(--color,red)
// 第一個變量不存在,用第二個

全局設置,穿透到裏面

<app-element></app-element>
<app-element></app-element>
<app-element></app-element>

<style>
  /* CSS Global */
  app-element:first-of-type {
    --color: orangered;
  }
</style>

<script>
  customElements.define("app-element", class extends HTMLElement {
    connectedCallback() {
      this.innerHTML = `
        <style>
          /* CSS Local */
          .element {
            display: inline-block;
            padding: 6px 20px;
            background: var(--color, steelblue);
            color: white;
          }
          span {
            font-weight: bold;
            vertical-align: super;
            font-size: small;
            color: gold;
          }
        </style>
        <div class="element">
          AppElement <span>New!</span>
        </div>
      `;
    }
  });
</script>

css 作用域

css 偽類,僅在定義了shadow DOM 有效

偽類 描述
:host 它允許我們設置自定義元素(組件自己的容器)的樣式。
:host(``css) 同上一個,但前提是它與中定義的選擇器匹配css
:host-context(``css) 同上,但前提是您有與選擇器匹配的父母css
<app-element></app-element>
<app-element disabled></app-element>
<div class="box">
  <app-element></app-element>
</div>

<script>
  customElements.define("app-element", class extends HTMLElement {
    constructor() {
      super();
      this.attachShadow({ mode: "open" });
    }
    connectedCallback() {
      this.shadowRoot.innerHTML = `
        <style>
          :host {
            display: inline-block;
            padding: 6px 20px;
            background: steelblue;
            color: white;
          }
          :host([disabled]) {
            background: #aaa;
          }
          :host-context(.box) {
            background: red;
          }
          span {
            font-weight: bold;
            vertical-align: super;
            font-size: small;
            color: gold;
          }
        </style>
        <div class="element">
          AppElement <span>New!</span>
        </div>
      `;
    }
  });
</script>

修改最外層的盒子的css

影子DOM操作事件

<app-element></app-element>

<script>
  customElements.define("app-element", class AppElement extends HTMLElement {
    constructor() {
      super();
      this.attachShadow({ mode: "open" });
    }

    sendMessage() {
      alert("Hello!");
    }

    connectedCallback() {
      this.shadowRoot.innerHTML = "<button>點我!</button>";
      this.button = this.shadowRoot.querySelector("button");
      this.button.addEventListener("click", () => this.sendMessage());
    }
    // 離開頁面刪除事件
    disconnectedCallback() {
      this.button.removeEventListener("click", () => this.sendMessage());
    }
  });
</script>

第二種方法

不用addEventListener

      this.button.onclick=()=>this.sendMessage()
 // 離開頁面刪除事件
    disconnectedCallback() {
      this.button.onclick=null;
    }

第三種方法

神奇的handleEvent函數

<app-element></app-element>

<script>
  customElements.define("app-element", class extends HTMLElement {
    constructor() {
      super();
      this.attachShadow({ mode: "open" });
    }

    handleEvent(event) {
      if (event.type === "click"){
          console.log(3)
      }
    }

    connectedCallback() {
      this.shadowRoot.innerHTML = "<button>👀 Press me!</button>";
      this.button = this.shadowRoot.querySelector("button");
      this.button.addEventListener("click", this);
    }

    disconnectedCallback() {
      this.button.removeEvenetListener("click", this);
    }
  });
</script>

我們發現當我們簡單的放置this 瀏覽器會奇特找到.handleEvent函數,存在就進行處理,這種方法我們可以通過檢測event.type,我們可以通過這種方法進行集中處理

自定義事件

選擇項 描述
detail 包含我們要傳輸的所有信息的對象。
bubbles 指示該事件是否應氣泡在DOM「到表面」或沒有。
composed 指示傳播是否可以遍歷Shadow DOM
cancelable 指示是否可以使用取消行為.preventDefault()

事件傳遞冒泡

<div class="box1">
  <div class="box2"></div>
</div>
<script>
  let box2 = document.querySelector('.box2');
  let box1 = document.querySelector('.box1');
  box2.addEventListener('click',()=>{
    box2.dispatchEvent(
      new CustomEvent('messages', {
        detail: {
          message: 'hello'
        },
        bubbles:true,
      })
    )
  })
  box1.addEventListener('messages',(e)=>{
    console.log(333);
    console.log(e.detail);
  })
</script>

bubbles=true 通過冒泡傳遞給父級,event.target 拿到dom元素,event.detail 拿到創建事件的數據

默認情況下

<div class="box1">
  <div class="box2">
    <div class="box3"></div>
  </div>
</div>
<script>
   let box1 = document.querySelector('.box1');
  let box2 = document.querySelector('.box2');
  let box3 = document.querySelector('.box3');
  box3.addEventListener('click',()=>{
    console.log(1);
    box3.dispatchEvent(
      new CustomEvent('messages', {
        detail: {
          message: 'hello'
        },
        bubbles:true,
      })
    )
  })
  box2.addEventListener('click',()=>{
    console.log(2);

  })
  box1.addEventListener('click',()=>{
    console.log(3);
  })    
</script>

我們發現默認情況下冒泡是從裡到外1,2,3

當我們在最外層添加

 box3.addEventListener("messages", (event) => {
    console.log(4);
  },{capture:true});

我們發現執行的順序為1,4,2,3

跨組件的通信

組件1發送數據

<first-element></first-element>

  customElements.define("first-element", class extends HTMLElement {
    constructor() {
      super();
      this.attachShadow({ mode: "open" });
    }

    handleEvent(event) {
      if (event.type === "click") {
        const MessageEvent = new CustomEvent("messages", {
          detail: {from: "Manz", message: ++this.i},
          bubbles: true,
          composed: true // 影子
        });
        this.dispatchEvent(MessageEvent);
      }
    }

    connectedCallback() {
      this.shadowRoot.innerHTML = `<button>點我</button>`;
      this.shadowRoot.querySelector("button").addEventListener("click", this);
    }
  });

接受傳遞來的數據

  customElements.define("second-element", class extends HTMLElement {
    constructor() {
      super();
      this.attachShadow({mode: "open"});
    }

    handleEvent(event) {
      if (event.type === "messages") {
        event.detail.from = "Robot";
        const data = event.detail;
        this.shadowRoot.innerHTML = `
        <div>
          From ${data.from}:
          <span style="color:red">${data.message}</span>
        </div>
      `;
      }
    }

    connectedCallback() {
      this.shadowRoot.innerHTML = `<div>No messages</button>`;
      document.addEventListener("messages", this);
    }
  });

這樣想不想兩個異步組件之間的通信