深入瞭解Web元件的方方面面

深入瞭解Web元件的方方面面

我們都有自己不想從事的專案。程式碼變得難以管理,範圍不斷髮展,快速修復應用在其他修復之上,並且結構在義大利麵條式程式碼的重壓下崩潰了。編碼可能是一件很麻煩的事情。

專案受益於使用具有單一職責的簡單、獨立的模組。模組化程式碼被封裝,因此無需擔心實現。只要您知道在給定一組輸入時模組將輸出什麼,您就不一定需要了解它是如何實現該目標的。

將模組化概念應用於單一程式語言很簡單,但Web開發需要多種技術組合。瀏覽器解析HTML 、 CSS和JavaScript以呈現頁面的內容、樣式和功能。

它們並不總是很容易混合,因為:

  • 相關程式碼可以拆分為三個或更多檔案,並且
  • 全域性樣式和JavaScript物件可能會以意想不到的方式相互干擾。

除了這些問題之外,語言執行時、框架、資料庫和伺服器上使用的其他依賴項也會遇到這些問題。

  1. 什麼是Web元件?
  2. Web元件簡史
  3. Web元件入門
  4. Web元件如何與其他元素互動
  5. 關於Web元件的批評和問題

什麼是Web元件?

Web元件是一種建立可在任何頁面上重用的封裝的、單一職責的程式碼塊的方法。

考慮HTML<video>標籤。給定URL ,檢視者可以使用諸如播放、暫停、後退、前進和調整音量等控制元件。

雖然您可以使用各種屬性和JavaScript API呼叫進行修改,但仍提供了樣式和功能。任意數量<video>元素可以放在其他標籤內,它們不會發生衝突。

如果您需要自己的自定義功能怎麼辦?例如,顯示頁面上字數的元素?還沒有HTML<wordcount>標籤。

React和Vue.js等框架允許開發人員建立Web元件,其中的內容、樣式和功能可以在單個JavaScript檔案中定義。這些解決了許多複雜的程式設計問題,但請記住:

  • 您必須學習如何使用該框架並隨著它的發展更新您的程式碼。
  • 為一個框架編寫的元件很少與另一個框架相容。
  • 框架的流行度起起落落。您將變得依賴於開發團隊和使用者的奇思妙想和優先事項。
  • 標準Web元件可以新增瀏覽器功能,這在單獨的JavaScript中是很難實現的(例如Shadow DOM)。

幸運的是,庫和框架中引入的流行概念通常會進入Web標準。這花了一些時間,但Web元件已經到來。

Web元件簡史

在許多特定於供應商的錯誤開始之後, Alex Russell在2011年的Fronteers Conference上首次引入了標準Web元件的概念。谷歌的Polymer庫(一個基於當前提案的polyfill)在兩年後問世,但早期的實現直到2016年才出現在Chrome和Safari中。

瀏覽器供應商花時間協商細節,但Web元件於2018年和20210年分別被新增到Firefox和Edge(當微軟切換到Chromium引擎時)。

可以理解的是,很少有開發人員願意或能夠採用Web元件,但我們最終通過穩定的API達到了良好的瀏覽器支援水平。並非一切都是完美的,但它們是基於框架的元件的日益可行的替代方案。

即使您現在還不願意放棄您最喜歡的,Web元件與每個框架都相容,並且API將在未來幾年內得到支援。

每個人都可以檢視預先構建的Web元件庫:

……但是編寫自己的程式碼更有趣!

本教程完整介紹了在沒有JavaScript框架的情況下編寫的Web元件。您將瞭解它們是什麼以及如何使它們適應您的Web專案。您將需要一些HTML5 、CSS和JavaScript 的知識。

Web元件入門

Web元件是自定義HTML元素,例如<hello-world></hello-world> 。該名稱必須包含一個破折號,以免與HTML規範中正式支援的元素髮生衝突。

您必須定義一個ES2015類來控制元素。它可以命名為任何名稱,但HelloWorld是常見的做法。它必須擴充套件HTMLElement介面,它表示每個HTML元素的預設屬性和方法。

注意: Firefox允許您擴充套件特定的HTML元素,例如HTMLParagraphElement、HTMLImageElement或HTMLButtonElement。這在其他瀏覽器中不受支援,並且不允許您建立Shadow DOM。

為了做任何有用的事情,該類需要一個名為connectedCallback()的方法,該方法在元素新增到文件時呼叫:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class HelloWorld extends HTMLElement {
// connect component
connectedCallback() {
this.textContent = 'Hello World!';
}
}
class HelloWorld extends HTMLElement { // connect component connectedCallback() { this.textContent = 'Hello World!'; } }
class HelloWorld extends HTMLElement {

  // connect component
  connectedCallback() {
    this.textContent = 'Hello World!';
  }

}

在此示例中,元素的文字設定為“Hello World”。必須使用CustomElementRegistry註冊該類才能將其定義為特定元素的處理程式:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
customElements.define( 'hello-world', HelloWorld );
customElements.define( 'hello-world', HelloWorld );
customElements.define( 'hello-world', HelloWorld );

現在,當您的JavaScript載入時,瀏覽器會將<hello-world>元素與您的HelloWorld <script type="module" src="./helloworld.js"></script> )。

您現在有一個自定義元素!

CodePen演示

這個元件可以像任何其他元素一樣在 CSS 中設定樣式:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
hello-world {
font-weight: bold;
color: red;
}
hello-world { font-weight: bold; color: red; }
hello-world {
  font-weight: bold;
  color: red;
}

新增屬性

該元件無益,因為無論如何都會輸出相同的文字。像任何其他元素一樣,我們可以新增HTML屬性:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<hello-world name="Craig"></hello-world>
<hello-world name="Craig"></hello-world>
<hello-world name="Craig"></hello-world>

這可能會覆蓋文字,因此“Hello Craig!” 被陳列。為此,您可以向HelloWorld類新增一個constructor()函式,該函式在建立每個物件時執行。它必須:

  1. 呼叫super()方法來初始化父 HTMLElement,以及
  2. 進行其他初始化。在這種情況下,我們將定義一個name屬性,該屬性設定為預設值“World”:
    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    class HelloWorld extends HTMLElement {
    constructor() {
    super();
    this.name = 'World';
    }
    // more code...
    class HelloWorld extends HTMLElement { constructor() { super(); this.name = 'World'; } // more code...
    class HelloWorld extends HTMLElement {
    
      constructor() {
        super();
        this.name = 'World';
      }
    
      // more code...

     

您的元件只關心name屬性。靜態observedAttributes()屬性應返回一組要觀察的屬性:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// component attributes
static get observedAttributes() {
return ['name'];
}
// component attributes static get observedAttributes() { return ['name']; }
// component attributes
static get observedAttributes() {
  return ['name'];
}

當在HTML中定義屬性或使用JavaScript更改屬性時,將呼叫attributeChangedCallback()方法它傳遞了屬性名稱、舊值和新值:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// attribute change
attributeChangedCallback(property, oldValue, newValue) {
if (oldValue === newValue) return;
this[ property ] = newValue;
}
// attribute change attributeChangedCallback(property, oldValue, newValue) { if (oldValue === newValue) return; this[ property ] = newValue; }
// attribute change
attributeChangedCallback(property, oldValue, newValue) {

  if (oldValue === newValue) return;
  this[ property ] = newValue;

}

在此示例中,只會更新name屬性,但您可以根據需要新增其他屬性。最後,您需要調整connectedCallback()方法中的訊息:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// connect component
connectedCallback() {
this.textContent = `Hello ${ this.name }!`;
}
// connect component connectedCallback() { this.textContent = `Hello ${ this.name }!`; }
// connect component
connectedCallback() {

  this.textContent = `Hello ${ this.name }!`;

}


CodePen 演示

生命週期方法

在Web元件狀態的整個生命週期中,瀏覽器會自動呼叫六個方法。此處提供了完整列表,儘管您已經在上面的示例中看到了前四個:

constructor()

它在元件第一次初始化時被呼叫。它必須呼叫super()並且可以設定任何預設值或執行其他預渲染過程。

靜態observedAttributes()

返回瀏覽器將觀察到的一組屬性。

attributeChangedCallback(propertyName, oldValue, newValue)

每當觀察到的屬性更改時呼叫。那些在HTML中定義的會被立即傳遞,但JavaScript可以修改它們:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
document.querySelector('hello-world').setAttribute('name', 'Everyone');
document.querySelector('hello-world').setAttribute('name', 'Everyone');
document.querySelector('hello-world').setAttribute('name', 'Everyone');

發生這種情況時,該方法可能需要觸發重新渲染。

connectedCallback()

當Web元件附加到文件物件模型時,將呼叫此函式。它應該執行任何需要的渲染。

disconnectedCallback()

當從文件物件模型中刪除Web元件時呼叫它。如果您需要清理,例如刪除儲存的狀態或中止Ajax請求,這可能很有用。

adoptedCallback()

當Web元件從一個文件移動到另一個文件時呼叫此函式。

Web元件如何與其他元素互動

Web元件提供了一些您在JavaScript框架中找不到的獨特功能。

Shadow DOM

雖然我們在上面構建的Web元件可以工作,但它不能免受外部干擾,CSS或JavaScript可以對其進行修改。同樣,您為元件定義的樣式可能會洩漏並影響其他元件。

Shadow DOM通過將一個單獨的DOM附加到Web元件來解決這個封裝問題:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const shadow = this.attachShadow({ mode: 'closed' });
const shadow = this.attachShadow({ mode: 'closed' });
const shadow = this.attachShadow({ mode: 'closed' });

模式可以是:

  1. “open” ——外層頁面的JavaScript可以訪問Shadow DOM(使用Element.shadowRoot ),或者
  2. “closed” ——Shadow DOM只能在Web元件中訪問。

Shadow DOM可以像任何其他DOM元素一樣進行操作:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
connectedCallback() {
const shadow = this.attachShadow({ mode: 'closed' });
shadow.innerHTML = `
<style>
p {
text-align: center;
font-weight: normal;
padding: 1em;
margin: 0 0 2em 0;
background-color: #eee;
border: 1px solid #666;
}
</style>
<p>Hello ${ this.name }!</p>`;
}
connectedCallback() { const shadow = this.attachShadow({ mode: 'closed' }); shadow.innerHTML = ` <style> p { text-align: center; font-weight: normal; padding: 1em; margin: 0 0 2em 0; background-color: #eee; border: 1px solid #666; } </style> <p>Hello ${ this.name }!</p>`; }
connectedCallback() {

  const shadow = this.attachShadow({ mode: 'closed' });

  shadow.innerHTML = `
    <style>
      p {
        text-align: center;
        font-weight: normal;
        padding: 1em;
        margin: 0 0 2em 0;
        background-color: #eee;
        border: 1px solid #666;
      }
    </style>

    <p>Hello ${ this.name }!</p>`;

}

<p>元素中呈現“Hello”文字併為其設定樣式。它不能被元件外的JavaScript或CSS修改,儘管字型和顏色等一些樣式是從頁面繼承的,因為它們沒有明確定義。


CodePen 演示

限定在此Web元件範圍內的樣式不能影響頁面上的其他段落,甚至其他<hello-world>元件。

請注意,CSS :host選擇器可以從Web元件中設定外部<hello-world>元素的樣式:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
:host {
transform: rotate(180deg);
}
:host { transform: rotate(180deg); }
:host {
  transform: rotate(180deg);
}

您還可以設定元素使用特定類時應用的樣式,例如<hello-world class="rotate90">:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
:host(.rotate90) {
transform: rotate(90deg);
}
:host(.rotate90) { transform: rotate(90deg); }
:host(.rotate90) {
  transform: rotate(90deg);
}

HTML模板

對於更復雜的Web元件,在指令碼中定義HTML可能變得不切實際。模板允許您在頁面中定義Web元件可以使用的HTML塊。這有幾個好處:

  1. 您可以調整HTML程式碼,而無需在JavaScript中重寫字串。
  2. 無需為每種型別建立單獨的JavaScript類,即可自定義元件。
  3. 在HTML中定義HTML更容易——並且可以在元件呈現之前在伺服器或客戶端上對其進行修改。

模板是在<template>標記中定義的,分配一個ID很實用,這樣您就可以在元件類中引用它。此示例使用三個段落顯示“Hello”訊息:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<template id="hello-world">
<style>
p {
text-align: center;
font-weight: normal;
padding: 0.5em;
margin: 1px 0;
background-color: #eee;
border: 1px solid #666;
}
</style>
<p class="hw-text"></p>
<p class="hw-text"></p>
<p class="hw-text"></p>
</template>
<template id="hello-world"> <style> p { text-align: center; font-weight: normal; padding: 0.5em; margin: 1px 0; background-color: #eee; border: 1px solid #666; } </style> <p class="hw-text"></p> <p class="hw-text"></p> <p class="hw-text"></p> </template>
<template id="hello-world">

  <style>
    p {
      text-align: center;
      font-weight: normal;
      padding: 0.5em;
      margin: 1px 0;
      background-color: #eee;
      border: 1px solid #666;
    }
  </style>

  <p class="hw-text"></p>
  <p class="hw-text"></p>
  <p class="hw-text"></p>

</template>

Web元件類可以訪問此模板、獲取其內容並克隆元素以確保您在任何使用它的地方建立唯一的DOM片段:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const template = document.getElementById('hello-world').content.cloneNode(true);
const template = document.getElementById('hello-world').content.cloneNode(true);
const template = document.getElementById('hello-world').content.cloneNode(true);

DOM可以直接修改並新增到Shadow DOM中:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
connectedCallback() {
const
shadow = this.attachShadow({ mode: 'closed' }),
template = document.getElementById('hello-world').content.cloneNode(true),
hwMsg = `Hello ${ this.name }`;
Array.from( template.querySelectorAll('.hw-text') )
.forEach( n => n.textContent = hwMsg );
shadow.append( template );
}
connectedCallback() { const shadow = this.attachShadow({ mode: 'closed' }), template = document.getElementById('hello-world').content.cloneNode(true), hwMsg = `Hello ${ this.name }`; Array.from( template.querySelectorAll('.hw-text') ) .forEach( n => n.textContent = hwMsg ); shadow.append( template ); }
connectedCallback() {

  const

    shadow = this.attachShadow({ mode: 'closed' }),
    template = document.getElementById('hello-world').content.cloneNode(true),
    hwMsg = `Hello ${ this.name }`;

  Array.from( template.querySelectorAll('.hw-text') )
    .forEach( n => n.textContent = hwMsg );

  shadow.append( template );

}


CodePen 演示

模板插槽Template Slots

插槽允許您自定義模板。假設您想使用Web元件<hello-world> ,但將訊息放在Shadow DOM中的<h1>標題中你可以寫這樣的程式碼:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<hello-world name="Craig">
<h1 slot="msgtext">Hello Default!</h1>
</hello-world>
<hello-world name="Craig"> <h1 slot="msgtext">Hello Default!</h1> </hello-world>
<hello-world name="Craig">

  <h1 slot="msgtext">Hello Default!</h1>

</hello-world>

(注意slot屬性。)

您可以選擇新增其他元素,例如另一個段落:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<hello-world name="Craig">
<h1 slot="msgtext">Hello Default!</h1>
<p>This text will become part of the component.</p>
</hello-world>
<hello-world name="Craig"> <h1 slot="msgtext">Hello Default!</h1> <p>This text will become part of the component.</p> </hello-world>
<hello-world name="Craig">

  <h1 slot="msgtext">Hello Default!</h1>
  <p>This text will become part of the component.</p>

</hello-world>

現在可以在您的模板中實現插槽:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<template id="hello-world">
<slot name="msgtext" class="hw-text"></slot>
<slot></slot>
</template>
<template id="hello-world"> <slot name="msgtext" class="hw-text"></slot> <slot></slot> </template>
<template id="hello-world">

  <slot name="msgtext" class="hw-text"></slot>

  <slot></slot>

</template>

將在名為“msgtext”的 <slot> 位置插入設定為“msgtext”(即<h1>)的元素slot屬性。 <p>沒有指定插槽名稱,但在下一個可用的未命名<slot>中使用。實際上,模板變為:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<template id="hello-world">
<slot name="msgtext" class="hw-text">
<h1 slot="msgtext">Hello Default!</h1>
</slot>
<slot>
<p>This text will become part of the component.</p>
</slot>
</template>
<template id="hello-world"> <slot name="msgtext" class="hw-text"> <h1 slot="msgtext">Hello Default!</h1> </slot> <slot> <p>This text will become part of the component.</p> </slot> </template>
<template id="hello-world">

  <slot name="msgtext" class="hw-text">
    <h1 slot="msgtext">Hello Default!</h1>
  </slot>

  <slot>
    <p>This text will become part of the component.</p>
  </slot>

</template>

事實並非如此簡單。Shadow DOM中的<slot>元素指向插入的元素。只能通過定位<slot>然後使用.assignedNodes() 方法返回內部子級陣列來訪問它們。更新的connectedCallback()方法:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
connectedCallback() {
const
shadow = this.attachShadow({ mode: 'closed' }),
hwMsg = `Hello ${ this.name }`;
// append shadow DOM
shadow.append(
document.getElementById('hello-world').content.cloneNode(true)
);
// find all slots with a hw-text class
Array.from( shadow.querySelectorAll('slot.hw-text') )
// update first assignedNode in slot
.forEach( n => n.assignedNodes()[0].textContent = hwMsg );
}
connectedCallback() { const shadow = this.attachShadow({ mode: 'closed' }), hwMsg = `Hello ${ this.name }`; // append shadow DOM shadow.append( document.getElementById('hello-world').content.cloneNode(true) ); // find all slots with a hw-text class Array.from( shadow.querySelectorAll('slot.hw-text') ) // update first assignedNode in slot .forEach( n => n.assignedNodes()[0].textContent = hwMsg ); }
connectedCallback() {

  const
    shadow = this.attachShadow({ mode: 'closed' }),
    hwMsg = `Hello ${ this.name }`;

  // append shadow DOM
  shadow.append(
    document.getElementById('hello-world').content.cloneNode(true)
  );

  // find all slots with a hw-text class
  Array.from( shadow.querySelectorAll('slot.hw-text') )

    // update first assignedNode in slot
    .forEach( n => n.assignedNodes()[0].textContent = hwMsg );

}


CodePen 演示

此外,您不能直接設定插入元素的樣式,儘管您可以定位Web元件中的特定插槽:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<template id="hello-world">
<style>
slot[name="msgtext"] { color: green; }
</style>
<slot name="msgtext" class="hw-text"></slot>
<slot></slot>
</template>
<template id="hello-world"> <style> slot[name="msgtext"] { color: green; } </style> <slot name="msgtext" class="hw-text"></slot> <slot></slot> </template>
<template id="hello-world">

  <style>
    slot[name="msgtext"] { color: green; }
  </style>

  <slot name="msgtext" class="hw-text"></slot>
  <slot></slot>

</template>

模板插槽有點不尋常,但一個好處是,如果JavaScript無法執行,您的內容將被顯示。此程式碼顯示了僅在Web元件類成功執行時才替換的預設標題和段落:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<hello-world name="Craig">
<h1 slot="msgtext">Hello Default!</h1>
<p>This text will become part of the component.</p>
</hello-world>
<hello-world name="Craig"> <h1 slot="msgtext">Hello Default!</h1> <p>This text will become part of the component.</p> </hello-world>
<hello-world name="Craig">

  <h1 slot="msgtext">Hello Default!</h1>
  <p>This text will become part of the component.</p>

</hello-world>

因此,您可以實現某種形式的漸進增強——即使它只是一條“You need JavaScript”的訊息!

宣告式Shadow DOM

上面的例子使用JavaScript構建了一個Shadow DOM。這仍然是唯一的選擇,但正在為Chrome開發一個實驗性的宣告性Shadow DOM。這允許伺服器端渲染並避免任何佈局變化或無樣式內容的閃爍。

HTML解析器檢測到以下程式碼,它建立一個與您在上一節中建立的Shadow DOM相同的Shadow DOM(您需要根據需要更新訊息):

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<hello-world name="Craig">
<template shadowroot="closed">
<slot name="msgtext" class="hw-text"></slot>
<slot></slot>
</template>
<h1 slot="msgtext">Hello Default!</h1>
<p>This text will become part of the component.</p>
</hello-world>
<hello-world name="Craig"> <template shadowroot="closed"> <slot name="msgtext" class="hw-text"></slot> <slot></slot> </template> <h1 slot="msgtext">Hello Default!</h1> <p>This text will become part of the component.</p> </hello-world>
<hello-world name="Craig">

  <template shadowroot="closed">
    <slot name="msgtext" class="hw-text"></slot>
    <slot></slot>
  </template>

  <h1 slot="msgtext">Hello Default!</h1>
  <p>This text will become part of the component.</p>

</hello-world>

該功能在任何瀏覽器中均不可用,並且不能保證它會到達Firefox或Safari。您可以找到有關宣告式Shadow DOM的更多資訊,polyfill很簡單,但請注意實現可能會發生變化。

Shadow DOM事件

您的Web元件可以像在頁面DOM中一樣將事件附加到Shadow DOM中的任何元素,例如偵聽所有內部子級上的單擊事件:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
shadow.addEventListener('click', e => {
// do something
});
shadow.addEventListener('click', e => { // do something });
shadow.addEventListener('click', e => {

  // do something

});

除非您stopPropagation ,否則該事件將冒泡到頁面DOM中,但該事件將被重定向。因此,它似乎來自您的自定義元素,而不是其中的元素。

在其他框架中使用Web元件

您建立的任何Web元件都可以在所有JavaScript框架中工作。他們都不知道或關心HTML元素——您的<hello-world>元件將被視為與<div>相同,並被放置到DOM中,類將在其中啟用。

custom-elements-everywhere.com提供了框架和Web元件註釋的列表。儘管在React.js有一些問題,但大多數都是完全相容的。可以在JSX中使用<hello-world>

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import React from 'react';
import ReactDOM from 'react-dom';
import from './hello-world.js';
function MyPage() {
return (
<>
<hello-world name="Craig"></hello-world>
</>
);
}
ReactDOM.render(<MyPage />, document.getElementById('root'));
import React from 'react'; import ReactDOM from 'react-dom'; import from './hello-world.js'; function MyPage() { return ( <> <hello-world name="Craig"></hello-world> </> ); } ReactDOM.render(<MyPage />, document.getElementById('root'));
import React from 'react';
import ReactDOM from 'react-dom';
import from './hello-world.js';

function MyPage() {

  return (
    <>
      <hello-world name="Craig"></hello-world> 
    </>
  );

}

ReactDOM.render(<MyPage />, document.getElementById('root'));

…但:

  • React只能將原始資料型別傳遞給HTML屬性(而不是陣列或物件)
  • React無法偵聽Web元件事件,因此您必須手動附加自己的處理程式。

關於Web元件批評和問題

Web元件有了顯著改進,但有些方面可能難以管理。

樣式和難點

樣式化Web元件會帶來一些挑戰,尤其是當您想覆蓋範圍樣式時。有很多解決方案:

  1. 避免使用Shadow DOM。您可以將內容直接附加到您的自定義元素,儘管任何其他JavaScript都可能意外或惡意更改它。
  2. 使用:host類。正如我們在上面看到的,當類應用於自定義元素時,作用域CSS可以應用特定的樣式。
  3. 檢視CSS自定義屬性(變數)。自定義屬性級聯到Web元件中,因此,如果您的元素使用var(--my-color) ,您可以在外部容器(例如:root --my-color ,它將被使用。自定義屬性級聯到Web元件中,因此,如果您的元素使用var(--my-color),您可以在外部容器(例如::root)中設定--my-color,然後使用它。
  4. 利用陰影部件。新的::part() 選擇器選擇器可以設定具有part屬性的內部元件的樣式,即<hello-world>元件內部的<h1 part="heading">可以使用選擇器hello-world::part(heading) 設定樣式。
  5. 傳入一串樣式。您可以將它們作為屬性傳遞給<style>塊。

沒有一個是理想的,您需要計劃其他使用者如何仔細定製您的Web元件。

忽略輸入

Shadow DOM中的任何<input><textarea><select>欄位都不會在包含表單中自動關聯。早期的Web元件採用者會將隱藏欄位新增到頁面DOM中,或者使用FormData介面來更新值。兩者都不是特別實用的,都會破壞Web元件的封裝。

新的ElementInternals介面允許Web元件連線到表單中,以便定義自定義值和有效性。它是用Chrome實現的,但是其他瀏覽器也可以使用polyfill

為了演示,您將建立一個基本的<input-age name="your-age"></input-age>元件。該類必須將靜態formAssociated值設定為true,並且可以選擇在外部窗體關聯時呼叫formAssociatedCallback() 方法:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// <input-age> web component
class InputAge extends HTMLElement {
static formAssociated = true;
formAssociatedCallback(form) {
console.log('form associated:', form.id);
}
// <input-age> web component class InputAge extends HTMLElement { static formAssociated = true; formAssociatedCallback(form) { console.log('form associated:', form.id); }
// <input-age> web component
class InputAge extends HTMLElement {

  static formAssociated = true;

  formAssociatedCallback(form) {
    console.log('form associated:', form.id);
  }

建構函式現在必須執行attachInternals()方法,該方法允許元件與表單和其他想要檢查值或驗證的JavaScript程式碼進行通訊:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
constructor() {
super();
this.internals = this.attachInternals();
this.setValue('');
}
// set form value
setValue(v) {
this.value = v;
this.internals.setFormValue(v);
}
constructor() { super(); this.internals = this.attachInternals(); this.setValue(''); } // set form value setValue(v) { this.value = v; this.internals.setFormValue(v); }
  constructor() {

    super();
    this.internals = this.attachInternals();
    this.setValue('');

  }

  // set form value

  setValue(v) {

    this.value = v;

    this.internals.setFormValue(v);

  }

ElementInternal的setFormValue()方法在此處為使用空字串初始化的父窗體設定元素的值(也可以傳遞具有多個名稱/值對的FormData物件)。其他屬性和方法包括:

  • form:父表單
  • labels:標記元件的元素陣列
  • 約束驗證API選項,例如willValidate、checkValidity和validationMessage

connectedCallback()方法像以前一樣建立Shadow DOM,但還必須監視欄位的更改,以便可以執行setFormValue()

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
connectedCallback() {
const shadow = this.attachShadow({ mode: 'closed' });
shadow.innerHTML = `
<style>input { width: 4em; }</style>
<input type="number" placeholder="age" min="18" max="120" />`;
// monitor input values
shadow.querySelector('input').addEventListener('input', e => {
this.setValue(e.target.value);
});
}
connectedCallback() { const shadow = this.attachShadow({ mode: 'closed' }); shadow.innerHTML = ` <style>input { width: 4em; }</style> <input type="number" placeholder="age" min="18" max="120" />`; // monitor input values shadow.querySelector('input').addEventListener('input', e => { this.setValue(e.target.value); }); }
 connectedCallback() {

    const shadow = this.attachShadow({ mode: 'closed' });

    shadow.innerHTML = `
      <style>input { width: 4em; }</style>
      <input type="number" placeholder="age" min="18" max="120" />`;

    // monitor input values
    shadow.querySelector('input').addEventListener('input', e => {
      this.setValue(e.target.value);
    });

  }

您現在可以使用此Web元件建立一個HTML表單,其作用與其他表單欄位類似:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<form id="myform">
<input type="text" name="your-name" placeholder="name" />
<input-age name="your-age"></input-age>
<button>submit</button>
</form>
<form id="myform"> <input type="text" name="your-name" placeholder="name" /> <input-age name="your-age"></input-age> <button>submit</button> </form>
<form id="myform">

  <input type="text" name="your-name" placeholder="name" />

  <input-age name="your-age"></input-age>

  <button>submit</button>

</form>

它有效,但不可否認,它感覺有點令人費解。在CodePen演示中檢視有關更多資訊,請參閱有關功能更強大的表單控制元件的文章

小結

在JavaScript框架的地位和能力不斷提高的時候,Web元件很難獲得一致和採用。如果您來自React、Vue或Angular,Web元件可能會顯得複雜而笨拙,尤其是當您缺少資料繫結和狀態管理等功能時。

有很多問題需要解決,但Web元件的未來是光明的。它們是框架無關的、輕量級的、快速的,並且可以實現單獨使用JavaScript無法實現的功能。

十年前,很少有人會在沒有jQuery的情況下解決網站問題,但瀏覽器供應商採用了優秀的部分,並新增了本地替代方案(如querySelector)。JavaScript框架也是如此,Web元件是第一個嘗試性的步驟。

評論留言