Witaj Web Components
Jeśli słyszałeś o komponentach sieciowych, ale nigdy sam ich nie stworzyłeś, trafiłeś we właściwe miejsce. Nadszedł czas! Zbudujmy razem komponent sieciowy!
Kiedy mam okazję uczyć ludzi o Web Components, zwykle lubię zacząć od czegoś prostego, co wielu z nas zna z „prawdziwego świata”. Zbudujmy więc „znacznik nazwy” komponentu sieci Web, który będzie wyglądał mniej więcej tak…
INFORMACJE: Co to jest komponent sieciowy? Termin „Komponenty sieciowe” to termin ogólny obejmujący zbiór kilku standardów W3C w HTML i CSS, które razem umożliwiają tworzenie i udostępnianie natywnych elementów HTML platformy internetowej wielokrotnego użytku (komponentów).
Krok zero
- Zacznij od podstawowego dokumentu HTML, który ma skrypt modułu JS wskazujący na (tymczasowo) pusty plik JavaScript.
- W treści dokumentu dodaj
name-tagelement z tekstem „Komponenty sieciowe” jako treść. To jest komponent sieciowy, który ostatecznie chcielibyśmy uruchomić. - Uruchom serwer WWW i przejdź do pliku HTML. Powinieneś zobaczyć tekst „Komponenty sieciowe” renderowany w Twojej przeglądarce.
<!DOCTYPE html>
<html>
<head>
<title>Hello Web Components</title>
</head>
<body>
<name-tag greeting="Hola">Web Components</name-tag>
<script type="module" src="index.js"></script>
</body>
</html>
// Nothing here yet. We'll add our Web Component code in the next step.
Krok pierwszy
- W pliku JavaScript zadeklaruj zachowanie
name-tagelementu niestandardowego, tworząc klasę o nazwieNameTag, która rozciąga się odHTMLElement. Wszystkie komponenty sieci Web dziedziczą poHTMLElementelementach wbudowanych, takich jakdiv,span, iinput. - Zarejestruj swój element w przeglądarce, wywołując
customElements.define(...), podając żądaną nazwę znacznika HTML iNameTagklasę jako argumenty. - Odśwież przeglądarkę i nadal powinieneś widzieć ten sam tekst co poprzednio. Jeśli jednak przyjrzysz się temu
name-tagelementowi, zobaczysz, że jest on nie tylko elementem , ale takżeHTMLElementjego konstruktorem .NameTag
class NameTag extends HTMLElement {
}
customElements.define('name-tag', NameTag);
Krok drugi
- Dodaj konstruktora do swojej klasy. Po wywołaniu funkcji zadzwoń
super(),this.attachShadow(...)aby utworzyć model Shadow DOM dla elementu niestandardowego. Shadow DOM jest jak posiadanie prywatnego dokumentu HTML, do którego może renderować tylko twój komponent. Przekaż opcje{ mode: ‘open’ }, aby główny cień i jego wewnętrzne elementy były nadal dostępne z zewnątrz za pośrednictwem JavaScript i narzędzi programistycznych. - Po dołączeniu Shadow DOM możesz uzyskać dostęp
this.shadowRooti ustawić jegoinnerHTMLwłaściwość na wybrany przez siebie kod HTML. - Odśwież przeglądarkę i zauważ, że
innerHTMLzawartość Shadow DOM jest teraz renderowana, ale zawartość samego elementu, tekst „Składniki sieciowe”, nie jest już renderowana. Gdzie to się podziało? - Otwórz inspektora i sprawdź, czy istnieje
#shadow-rootwęzeł, który możesz sprawdzić, aby zobaczyć, co podałeś jakoinnerHTML. Pamiętaj, że Twoja treść nadal znajduje się w DOM, nawet jeśli nie jest renderowana. Powodem, dla którego nie jest renderowany, jest to, że przeglądarka nie wie, jak skomponować zawartość Light DOM z Shadow DOM. Naprawimy to dalej.
class NameTag extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = 'Rendering from Shadow DOM';
}
}
customElements.define('name-tag', NameTag);
Krok trzeci
- Popraw kod HTML, który jest umieszczany w katalogu głównym cienia, tak aby zawierał element
<slot>. To mówi przeglądarce, jak skomponować Light DOM i Shadow DOM razem. Gniazdo zapewnia lokalizację do „projekcji” lub renderowania zawartości Light DOM w Shadow DOM. Treść nadal istnieje w Light DOM, ale jest renderowana tak, jakby znajdowała się w miejscu, w którym się znajduje. - Odśwież przeglądarkę i zauważ, że zawartość Light i Shadow DOM jest teraz odpowiednio skomponowana.
class NameTag extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = 'HELLO <slot></slot>';
}
}
customElements.define('name-tag', NameTag);
Krok czwarty
- Aby nasz
greetingatrybut działał, musimy powiedzieć platformie, że istnieje atrybut, którygreetingchcemy obserwować. Utwórz statyczny moduł pobierający o nazwieobservedAttributes, który zwraca tablicę nazw atrybutów do obserwowania przez platformę. Tablica powinna zawierać pojedynczą nazwę atrybutu:['greeting']. - Następnie zaimplementuj funkcję
attributeChangedCallback(), którą platforma może wywołać za każdym razem, gdy zmieni się którykolwiek z jej obserwowanych atrybutów. - Dodaj moduł pobierający/ustawiający właściwość, aby zapewnić dostęp do właściwości atrybutu, ponieważ większość elementów HTML ma zarówno właściwości, jak i atrybuty. Dzięki temu nasz niestandardowy element będzie wyglądał jak każdy inny na platformie i będzie działał poprawnie z popularnymi frameworkami front-end, które ustawiają zarówno atrybuty, jak i właściwości. Moduły pobierające i ustawiające mogą po prostu delegować do interfejsów API
getAttribute()i .setAttribute()HTMLElement - Wyodrębnij
renderfunkcję, która pobiera komponent jako dane wejściowe i wywołaj ją z programu,attributeChangedCallback()aby mogła zaktualizować renderowanie, gdy zmieni się stan elementu. Usuń kod ustawionyinnerHTMLw konstruktorze. To nie jest już potrzebne. - Możemy również wprowadzić
connectedCallback().platformę, która wywoła to, gdy element zostanie połączony z dokumentem. Użyjemy tego, aby upewnić się, że istnieje wartość domyślna,greetingjeśli nie została ustawiona do czasu połączenia elementu. - Odśwież przeglądarkę, aby zobaczyć, że
greetingatrybut zaczyna obowiązywać. Eksperymentuj, używając narzędzi do debugowania, aby ustawićgreetingwłaściwość i atrybut. Spróbuj umieścić punkty przerwania w plikuattributeChangedCallback().
const render = x => `${x.greeting.toUpperCase()} <slot></slot>`;
class NameTag extends HTMLElement {
static get observedAttributes() {
return ['greeting'];
}
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
get greeting() {
return this.getAttribute('greeting');
}
set greeting(value) {
this.setAttribute('greeting', value);
}
connectedCallback() {
if (!this.greeting) {
this.greeting = 'Hello';
}
}
attributeChangedCallback(name, oldValue, newValue) {
this.shadowRoot.innerHTML = render(this);
}
}
customElements.define('name-tag', NameTag);
- Poprawmy naszą
renderfunkcję, aby zapewniała bardziej realistyczną strukturę. - Odśwież przeglądarkę, aby upewnić się, że nowa struktura renderuje się prawidłowo.
const render = x => `
<div part="header" class="header">
<h3 part="greeting">${x.greeting.toUpperCase()}</h3>
<h4 part="message">my name is</h4>
</div>
<div part="body" class="body">
<slot></slot>
</div>
<div part="footer" class="footer"></div>
`;
Krok szósty
- Wykorzystując nowe standardy Constructible StyleSheets i Adopted StyleSheets, utwórz instancję
CSSStyleSheeti wywołaj jąreplaceSync(), aby ustawić jej tekst CSS. - W swoim konstruktorze elementów dołącz własne style do istniejącego
adoptedStyleSheetsplikushadowRoot. - Odśwież przeglądarkę, aby zobaczyć w pełni stylizowany komponent.
const styles = new CSSStyleSheet();
styles.replaceSync(`
:host {
--default-color: red;
--default-radius: 6px;
--default-depth: 5px;
display: inline-block;
contain: content;
color: white;
background: var(--color, var(--default-color));
border-radius: var(--radius, var(--default-radius));
min-width: 325px;
text-align: center;
box-shadow: 0 0 var(--depth, var(--default-depth)) rgba(0,0,0,.5);
}
.header {
margin: 16px 0;
position: relative;
}
h3 {
font-weight: bold;
font-family: sans-serif;
letter-spacing: 4px;
font-size: 32px;
margin: 0;
padding: 0;
}
h4 {
font-family: sans-serif;
font-size: 18px;
margin: 0;
padding: 0;
}
.body {
background: white;
color: black;
padding: 32px 8px;
font-size: 42px;
font-family: cursive;
}
.footer {
height: 16px;
background: var(--color, var(--default-color));
border-radius: 0 0 var(--radius, var(--default-radius)) var(--radius, var(--default-radius));
}
`);
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.adoptedStyleSheets = [
...this.shadowRoot.adoptedStyleSheets,
styles
];
}
Czytaj dalej, aby wejść nieco głębiej i odkryć dodatkowe zasoby edukacyjne i biblioteki.
Idąc głębiej
Właściwości niestandardowe CSS (znane również jako zmienne CSS)
Powszechnym sposobem nadawania stylów elementom niestandardowym jest oparcie stylów komponentów na właściwościach niestandardowych CSS (inaczej Zmienne CSS). Właściwości niestandardowe są deklarowane z przedrostkiem podwójnego myślnika i przywoływane przez var(...)funkcję. Odwołując się do zmiennej, możesz również podać wartość rezerwową, która sama może być inną zmienną. Możesz zobaczyć tę technikę stosowaną w powyższym CSS. Aby się z tym pobawić, utwórz kilka <name-tag>elementów na swojej stronie, a następnie użyj inspektora stylu przeglądarki, aby ustawić ——color, ——depth, i——radiuswłaściwości poszczególnych elementów lub elementów nadrzędnych. Mimo że Shadow DOM hermetyzuje style, właściwości niestandardowe CSS domyślnie „przebijają” granice Shadow DOM. Umożliwia to stworzenie systemu motywów, który działa w całej bibliotece komponentów lub aplikacji. I pamiętaj, właściwości niestandardowe CSS mogą być używane razem z CSS calc(...)w celu uzyskania niesamowitych efektów.
- Przeczytaj więcej o niestandardowych właściwościach CSS na MDN.
- Przeczytaj więcej o CSS Calc na MDN.
Style Shadow DOM mogą również wykorzystywać specjalne selektory, takie jak :host selektor, który celuje w sam element. Najlepszą praktyką jest konfigurowanie stylów hosta dla ustawień domyślnych displayi disabledstanów. Sprawdź containwłaściwość CSS, aby dowiedzieć się, jak poprawić wydajność renderowania komponentów. Jeśli masz specjalne style dla elementów umieszczonych w treści Twojego elementu, możesz je określić za pomocą ::slotted()selektora.
- Przeczytaj więcej o :host na MDN.
- Przeczytaj więcej o ::slotted na MDN.
- Przeczytaj więcej o CSS Contain na MDN.
Ponieważ adoptedStyleSheetsnie jest jeszcze zaimplementowany we wszystkich przeglądarkach (patrzę na ciebie, Safari!), dla wszystkich komponentów produkcyjnych, które tworzysz, będziesz chciał wykrywać funkcje i w razie potrzeby powracać do wstrzykiwania elementów stylu. Jest to coś, co wiele bibliotek Web Components (np. FAST ) obsługuje automatycznie.
- Przeczytaj więcej o przyjętych arkuszach stylów na MDN.
Być może zauważyłeś, że kilka elementów w powyższym DOM-cieniu ma partatrybut. Dzięki temu programista komponentu internetowego może zadeklarować części komponentu, które mogą być stylizowane zewnętrznie przez konsumentów komponentu. Aby to wypróbować, utwórz kilka <name-tag>elementów na swojej stronie, każdy z innym rozszerzeniem class. Następnie utwórz CSS, który kieruje części na podstawie selektora klasy i dostosowuje style powitania. Oto jak to może wyglądać:
.large-greeting::part(greeting) {
font-size: 64px;
}
Cykl życia komponentu internetowego
W naszym NameTagżywiole wykorzystaliśmy connectedCallback(...), który jest jednym ze standardowych haków cyklu życia komponentu sieciowego. Ale to nie jedyny. Oto lista dostępnych wywołań zwrotnych cyklu życia, których możesz użyć w swoich komponentach:
constructor()— Uruchamia się, gdy element jest tworzony lub aktualizowany.connectedCallback()— Uruchamia się, gdy element jest wstawiany do DOM.disconnectedCallback()— Uruchamia się, gdy element jest usuwany z modelu DOM.attributedChangedCallback(attrName, oldValue, newValue)— Uruchamia się za każdym razem, gdy zmienia się jeden z niestandardowych atrybutów elementu.adoptedCallback()— Uruchamia się, gdy element jest przenoszony z bieżącegodocumentdo nowegodocumentpoprzez wywołanieadoptNode(…)API.
Jak wspomniano wcześniej, ilość szablonów wymaganych podczas tworzenia prostego komponentu sieciowego wydaje się nieco za duża. Dzieje się tak dlatego, że standardy Web Component zapewniają tylko funkcje niskiego poziomu, z niewielką ilością opinii lub bez nich. Ale co by było, gdyby wprowadzono niewielki zestaw opcjonalnych opinii? Czy może to zmniejszyć ilość kodu i zapewnić inne korzyści? Wielu ludzi budujących Web Component tak uważa i dlatego korzysta z jakiejś małej biblioteki pomocniczej. Na zakończenie tego wpisu chciałbym pokazać ten sam komponent, zbudowany z FAST , który usprawnia proces tworzenia Web Componenta i zapewnia dodatkowe narzędzia do budowania bardziej złożonych rozwiązań. Oto NameTagkomponent sieciowy zaimplementowany z FAST , przy użyciu TypeScript:
import {
attr,
css,
customElement,
FASTElement,
html
} from "@microsoft/fast-element";
// Create a reactive template based on the element's state.
const template = html<NameTag>`
<div part="header" class="header">
<h3 part="greeting">${x => x.greeting.toUpperCase()}</h3>
<h4 part="message">my name is</h4>
</div>
<div part="body" class="body">
<slot></slot>
</div>
<div part="footer" class="footer"></div>
`;
// Create styles that automatically use adopted style sheets when present.
const styles = css`
:host {
--default-color: red;
--default-radius: 6px;
--default-depth: 5px;
display: inline-block;
contain: content;
color: white;
background: var(--color, var(--default-color));
border-radius: var(--radius, var(--default-radius));
min-width: 325px;
text-align: center;
box-shadow: 0 0 var(--depth, var(--default-depth)) rgba(0,0,0,.5);
}
.header {
margin: 16px 0;
position: relative;
}
h3 {
font-weight: bold;
font-family: sans-serif;
letter-spacing: 4px;
font-size: 32px;
margin: 0;
padding: 0;
}
h4 {
font-family: sans-serif;
font-size: 18px;
margin: 0;
padding: 0;
}
.body {
background: white;
color: black;
padding: 32px 8px;
font-size: 42px;
font-family: cursive;
}
.footer {
height: 16px;
background: var(--color, var(--default-color));
border-radius: 0 0 var(--radius, var(--default-radius)) var(--radius, var(--default-radius));
}
`;
// Define the element with a name, template, and styles.
@customElement({
name: 'name-tag',
template,
styles
})
class NameTag extends FASTElement { // Remove boilerplate w/ base class.
@attr greeting = 'Hello'; // A reactive HTML attribute with a default.
}
Zauważ, jak znika cała płyta kotłowa? Oto kilka rzeczy, które FAST robi dla Ciebie:
- Szablony — FAST zapewnia wysokowydajny, oparty na reaktywności silnik szablonów, w tym obsługę zaawansowanej maszyny MVVM na dużą skalę.
- Style — FAST automatycznie wykrywa obecność przyjętych arkuszy stylów i używa ich, jeśli to możliwe. Buforuje również i ponownie wykorzystuje instancje arkuszy stylów w instancjach Web Component w celu poprawy wydajności i zarządzania pamięcią.
- FASTElement — klasa podstawowa automatycznie konfiguruje Shadow DOM i dołącza do cyklu życia, aby obsłużyć renderowanie za pomocą dostarczonego szablonu. Dekorator zapewnia deklaratywny sposób łączenia szablonu, stylów i klas, jednocześnie rejestrując je na platformie przy użyciu podanej nazwy.
- Atrybuty — Zamiast ręcznie deklarować moduł pobierający/ustawiający, konfigurować tablicę
observedAttributesi obsługiwać wartości domyślne oraz wywołania zwrotne zmiany atrybutów, po prostu dekorujesz pole@attriFASTElementobsługujesz to wszystko za Ciebie.
Chcesz dowiedzieć się więcej lub dołączyć do rosnącej społeczności FAST i Web Components?
- Znajdź FAST na GitHub.
- Zapoznaj się z pełną dokumentacją FAST.
- Subskrybuj blog FAST.
- Dołącz do serwera FAST Discord.

![Czym w ogóle jest lista połączona? [Część 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































