PolymerElement je mrtvý, ať žije LitElement!

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 funkce
  • fromAttribute funkce
  • toAttribute 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!