Dobrý den, moji přátelé! Konečně jsme se dostali k hlavnímu tématu této série:LitElement .
Nová třída pro ovládání webových komponent
Chcete-li pochopit, co LitElement je a co pro nás dělá, se vrátíme tam, kde jsme skončili v předchozím příspěvku. Připomeňme si, co jsme dělali minule, použili jsme lit-html k vytvoření webové komponenty schopné opětovného vykreslení, když se změní hodnota vlastnosti komponenty.
Abychom toho dosáhli, museli jsme vytvořit třídu s konstruktorem, který byl zodpovědný za vytvoření stínového DOM a odrážení hodnot atributů HTML do vlastností komponenty. Aby se pak komponenta znovu vykreslila pokaždé, když se vlastnost změní, museli jsme pro každou z nich napsat setter a zavolat lit-html render
funkce uvnitř tohoto nastavovače.
A v neposlední řadě jsme museli napsat kód, abychom udrželi vlastnosti a atributy HTML v synchronizaci.
To vše má za následek opakující se kód, který se zvyšuje podle počtu vlastností, které komponenta má. Chcete-li vytvořit lepší prostředí pro vývojáře, Polymer tým si myslel, že by bylo dobré mít třídu, která za nás celou tuto zátěž zvládne. Stejně jako vyrobili PolymerElement , ale tentokrát se museli vyvinout a využít jejich úžasného lit-html , takže by to nebyla nová verze PolymerElement , muselo to být úplně nové, a tak vytvořili LitElement .
LitElement nejen nás osvobozuje od opakujícího se kódu, ale také vytváří lit-html vykreslování ještě efektivnější, protože se to děje asynchronně.
Takže LitElement je odlehčená třída pro vytváření webových komponent. Zvládá za nás veškerý opakující se kód potřebný k:
- použijte stínový DOM
- udržujte synchronizaci atributů HTML a vlastností komponent
- efektivně vykreslovat (pomocí lit-html ) komponenta pokaždé, když se vlastnost změní
Podívejme se na minimální kód LitElement potřeby:
// Import the LitElement base class and html helper function
import { LitElement, html } from 'lit-element';
// Import other element if it needed
import 'package-name/other-element.js';
// Extend the LitElement base class
class MyElement extends LitElement {
/**
* Implement `render` to define a template for your element.
*
* You must provide an implementation of `render` for any element
* that uses LitElement as a base class.
*/
render() {
/**
* `render` must return a lit-html `TemplateResult`.
*
* To create a `TemplateResult`, tag a JavaScript template literal
* with the `html` helper function:
*/
return html`
<!-- template content -->
<p>A paragraph</p>
<other-element></other-element>
`;
}
}
// Register the new element with the browser.
customElements.define('my-element', MyElement);
Jak jste viděli, jediným požadavkem je implementace render
funkce, která kreslí uživatelské rozhraní komponenty. Tato funkce musí vrátit TemplateResult
a to proto, že bude volat render
funkce poskytovaná lit-html (všimněte si, že importujeme také html
funkce). To je velmi důležité, protože vše, co jsme viděli v předchozích příspěvcích lit-html platí pro LitElement .
Mohli bychom shrnout LitElement ve velmi zjednodušeném vzorci:
Funkce vykreslování LitElement
render
Funkce má v LitElement velký význam protože určuje, jak bude komponenta vypadat. Když uvidíte definovanou šablonu, měli byste pochopit, jak bude komponenta namalována v každé situaci. Neexistuje žádné jiné místo, kde by bylo možné upravit nebo aktualizovat uživatelské rozhraní komponenty. A co víc, kdykoli se změní vlastnost (změní se stav komponenty) LitElement zavolá render
funkce pro aktualizaci reprezentace komponenty. Ukazuje se tedy, že uživatelské rozhraní je vyjádřeno jako funkce stavu komponenty.
Podle tohoto funkčního přístupu by šablona měla být zapsána jako čistá funkce vlastností komponenty, takže:
- Nemění to stav komponenty
- Nemá žádné vedlejší účinky
- Záleží pouze na vlastnostech komponenty
- Vždy vrací stejnou hodnotu, pokud se vlastnosti nezměnily
Výsledkem je skvělý zážitek pro vývojáře, protože se nemusíte starat o to, jak aktualizovat komponentu, když se něco změní. Bude znovu vykreslen podle nového stavu a dopad vykreslování na výkon nemá žádnou hodnotu kvůli účinnosti lit-html plus asynchronní optimalizace přidaná LitElement .
Bez tohoto řešení bychom komponentu vykreslili jednou (poprvé) a napsali bychom další kód pro aktualizaci některé části komponenty pro každou možnou změnu stavu a obecně by tento kód měl mnoho podmínek a úvah. Z dlouhodobého hlediska se aktualizace vizuální reprezentace v závislosti na změnách stavu komponenty stává obtížným úkolem a je třeba udržovat spoustu kódu.
Také v přístupu re-render je uživatelské rozhraní komponenty definováno deklarativně a na jednom místě. Klasický přístup (aktualizace částí uživatelského rozhraní) je nezbytný a logika je distribuována v mnoha funkcích.
Naše první komponenta LitElement
Pamatujete si <password-checker>
komponentu, kterou jsme vytvořili pomocí lit-html v předchozím příspěvku?
import { html, render } from 'lit-html';
class PasswordChecker extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: 'open'});
this.password = this.getAttribute('password');
}
get password() { return this._password; }
set password(value) {
this._password = value;
this.setAttribute('password', value);
this.update();
}
update() {
render(this.template(), this.shadowRoot, {eventContext: this});
}
isValid(passwd) {
const re = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{4,}/;
return re.test(passwd);
}
template() {
return html`
<span>Your password is <strong>${this.isValid(this.password) ? 'valid 👍' : 'INVALID 👎'}</strong></span>
${this.isValid(this.password) ?
html`<div>Strength: <progress value=${this.password.length-3} max="5"</progress></div>` : ``}`;
}
}
customElements.define('password-checker', PasswordChecker);
Nyní se podívejme, jak je tato komponenta implementována pomocí LitElement .
import { LitElement, html } from 'lit-element';
class PasswordChecker extends LitElement {
static get properties() {
return {
password: String
}
}
isValid(passwd) {
const re = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{4,}/;
return re.test(passwd);
}
render() {
return html`
<span>Your password is <strong>${this.isValid(this.password) ? 'valid 👍' : 'INVALID 👎'}</strong></span>
${this.isValid(this.password) ?
html`<div>Strength: <progress value=${this.password.length-3} max="5"</progress></div>` : ``}`;
}
}
customElements.define('password-checker', PasswordChecker);
První věc, kterou si všimnete, je, že neexistuje žádný konstruktor. V tomto případě to nepotřebujeme. Nemusíme přidávat kořen stínů protože LitElement dělá to pro nás. Když LitElement vykreslí komponentu, volá lit-html 's render
funkce a tato funkce očekává argument, který je uzlem, kde se má komponenta namalovat. LitElement vytvoří kořen stínů pro komponentu a předá ji lit-html 's render
funkce. Je to velmi podobné tomu, co jsme udělali v update
funkce lit-html verze.
Pokud z jakéhokoli důvodu nechceme, aby naše komponenta používala stínový DOM, můžeme přepsat createRenderRoot
a vrátí this
hodnota.
createRenderRoot() {
return this;
}
Ačkoli LitElement dělá mnoho věcí za nás, také nám umožňuje přizpůsobit výchozí chování přepsáním některých funkcí. Uvidíme, že LitElement je velmi flexibilní.
Vlastnosti a aktualizace
Naše lit-html verzi <password-checker>
měl setter pro vlastnost password
a v této funkci jsme aktualizovali atribut HTML a vyvolali render
funkce. LitElement dělá to samé pro nás. Všechny skvělé věci se stanou, když definujeme funkci getter properties
:
static get properties() {
return {
password: String
}
}
Každá vlastnost, která je definována uvnitř této funkce, bude řízena LitElement takže změna jeho hodnoty způsobí opětovné vykreslení komponenty.
Tedy pro každou deklarovanou vlastnost LitElement poskytne:
- pozorovaný atribut
- Přístupci
hasChanged
funkcefromAttribute
funkcetoAttribute
funkce
Podívejme se podrobně, které to jsou:
Pozorovaný atribut
Předpokládejme, že vaše komponenta má vlastnost nazvanou birthYear
, budete moci použít atribut birthyear
v označení
<my-comp birthyear="1973">
a LitElement přiřadí tuto hodnotu vlastnosti, ale předtím převede String
hodnotu k typu vlastnosti (Number
v tomto případě) pomocí fromAttribute
funkce.
👉 Všimněte si, že ve výchozím nastavení je název atributu název vlastnosti psaný malými písmeny. Můžete jej změnit pomocí attribute
klíč v definici vlastnosti:
static get properties() {
return {
birthYear: {
type: Number,
// the observed attribute will be birth-year instead of birthyear
attribute: 'birth-year'
}
}
}
I když ve výchozím nastavení LitElement předává hodnoty z atributů do vlastností, opak není pravdou. Pokud chcete, aby se změna hodnoty vlastnosti projevila v atributu HTML, musíte to výslovně sdělit pomocí klíče reflect
.
static get properties() {
return {
birthYear: {
type: Number,
// the observed attribute will be birth-year instead of birthyear
attribute: 'birth-year'
reflect: true
}
}
}
se změnilo
Je to funkce, která kontroluje, zda se nová hodnota liší od předchozí hodnoty. V takovém případě vrátí true
.
⚠️ Buďte opatrní s hodnotami, které jsou objekty nebo pole, protože porovnání se provádí na nejvyšší úrovni, neprovádí povrchní srovnání, takže pokud vyhodnotíte obj1 === obj2
srovnáváš reference. V takovém případě byste měli přepsat hasChanged
udělat správné srovnání.
fromAttribute
Je to funkce, která převádí String
hodnotu pozorovaného atributu ke skutečnému typu vlastnosti. Vlastní převodník můžete poskytnout přepsáním fromAttribute
.
toAtribut
Je to funkce používaná k převodu hodnoty vlastnosti na String
hodnotu, aby ji bylo možné přiřadit k pozorovanému atributu ve značkovacím kódu (HTML). Tuto funkci používá LitElement když byla vlastnost nastavena na reflect
.
Pokud potřebujete vlastní převodník, přepište toAttribute
.
Přístupové prvky
LitElement generuje přístupové objekty, getter a nastavovač , pro deklarované vlastnosti. V nastavení spoléhá téměř na všechna 'kouzla' což způsobí, že se komponenta znovu vykreslí při změně vlastnosti. Nejprve zkontroluje, zda se vlastnost změnila (vyvolejte hasChanged
funkce) a pokud tomu tak je, spustí aktualizaci uživatelského rozhraní.
Také pokud se vlastnost odráží v atributu, setter aktualizuje pozorovaný atribut pomocí funkce toAttribute
.
Pokud poskytnete svůj vlastní getter nebo setter nebo obojí a poté LitElement nevytvoří pro tuto vlastnost žádný přístupový objekt. Jen mějte na paměti, že pokud napíšete svůj vlastní setter a chcete, aby změna způsobila opětovné vykreslení, budete muset udělat stejné věci, které dělá getter ** LitElement ** .
Můžete se také vyhnout automaticky generovanému getteru a setteru pomocí klíče noAccessor
.
static get properties() {
return { birthYear: { type: Number, noAccessor: true } };
}
Shrnu tyto body na příkladu:
// properties getter
static get properties() {
return {
// by default, every declared property:
// - has an observed attribute,
// - when the attribute changes, it updates the property
// - has a getter and a setter
// - changes in value triggers a render update
// - has a default hasChanged function
// - has default converters: fromAttribute, toAttribute
// - all of this can be customized
firstName: { type: String }, // type is the minimum required information
lastName: { type: String,
attribute: 'last-name'
},
enrolled: { type: Boolean },
address: { type: Object,
reflect: false,
noAccessor: true,
hasChanged(newValue, oldValue) {
return newValue.zipCode != oldValue.zipCode;
}
},
age: {
converter: {
toAttribute(value) {
return String(value);
}
fromAttribute(value) {
return Number(value);
}
}
}
};
}
Jako poslední poznámky jsme zaznamenali, že properties
getter je velmi podobný properties
getter, který jsme použili v PolymerElement ale LitElement verze postrádá následující funkce:
počáteční hodnota
V PolymerElement 's properties
getter můžeme přiřadit počáteční hodnotu vlastnosti, ale to není možné v LitElement , musíme to udělat v konstruktoru.
// PolymerElement
static get properties() {
return { birthYear: { type: Number, value: 1973 } };
}
// LitElement
constructor() {
super(); // Don't forget to call super() !!!
this.birthYear = 1973;
}
pozorované vlastnosti
LitElement nemá žádné pozorované atributy. Pomocí setteru můžete provádět akce při změně vlastnosti.
// PolymerElement
static get properties() {
return { birthYear: { type: Number, observer: '_yearChanged' } };
}
// LitElement
set birthYear(value) {
// Code to check if property hasChanged
// and request UI update should go here
// ...
this._birthYear = value; // private _birthYear with getter birthYear
this._yearChanged();
}
vypočítané vlastnosti
LitElement nemá vypočítané vlastnosti. K dosažení stejného výsledku použijte getry.
// PolymerElement
static get properties() {
return { birthYear: { type: Number },
age: { type: Number, computed: '_computeAge(birthYear)' }
};
}
// LitElement
get age() {
return (new Date()).getFullYear() - this.birthYear;
}
No, už toho máme dost.
Zatím první díl o LitElement . V dalším příspěvku vám podrobně řeknu, jak probíhá asynchronní proces vykreslování a životní cyklus LitElement komponent.
Uvidíme se!