Templates en Lit-Element

  • Por
Los templates en lit-element: Cómo configurar la vista de un componente, Web Component, basado en LitElement.

El template del componente sirve para definir su parte representacional. Podemos llamarle "vista" o "parte visual" también en español. Es aquel contenido y aspecto que tendrá el componente cuando sea utilizado. El template incluye por tanto HTML para el contenido y CSS para su presentación, pero además en un template somos capaces de interpolar el contenido de las propiedades del elemento de un modo reactivo. Esto quiere decir que, cuando se actualicen las propiedades del componente con nuevos valores, el template se actualizará automáticamente para reflejar esos cambios.

Como hemos mencionado ya en el Manual de LitElement, una de las características más representativas de esta clase base para la creación de Web Components es su elevado rendimiento. Esto lo consigue gracias a que él sabe qué porción del template alberga cada propiedad, de modo que, si cambia una propiedad determinada, consigue actualizar únicamente aquella parte del template que realmente ha cambiado. Esta funcionalidad la realiza a demás de modo asíncrono, lo que permite al navegador seguir trabajando con otros asuntos durante el ciclo de vida de los componentes. Todas estas características de funcionamiento están heredadas de LitHTML, pues LitElement es una capa por encima, orientada a el trabajo dentro de Web Components.

Nota: Para obtener mayores detalles técnicos sobre cómo consigue implementar los templates reactivos con mayor rendimiento que otros frameworks y librerías consultar el artículo de LitHTML.

Método render

Realmente en el artículo anterior ya pudimos ver un primer componente de LitElement, donde se encontraba el método render() que debemos implementar para definir el template. Vamos a ahondar un poco más sobre su forma y posibilidades, pero antes que nada veamos su código.

import { LitElement, html } from 'lit-element';

export class MyCounter extends LitElement {
  render() {
    return html`
      <h1>Componente my-counter</h1>
    `;
  }
}

customElements.define('my-counter', MyCounter);

El código anterior define un componente completo basado en LitElement. Verás que como contenido de la clase que implementa el componente está el método render(), que usamos para definir el template. En este método se especifica básicamente una cadena, de tipo "template string" de Javascript, la cual se encierra mediante el caracter que usamos para el acento abierto (en dirección contraria que los acentos habituales del español).

Para generar el template, dentro del método render, nos apoyamos en una función llamada "html", que no es más que un Tagged Template String Literal, la verdadera tecnología Javascript estándar que está detrás del secreto del rendimiento de LitHTML y por tanto de LitElement. Realmente no necesitamos saber mucho sobre esa función, apenas cuál es la sintaxis que necesitamos utilizar para definir los aspectos de nuestro template. De momento sólo tenemos HTML, pero enseguida vamos a producir un template algo más complejo.

Finalmente, el método render() devuelve el template generado.

Sintaxis válida en los templates

LitElement y la función "html" que usamos para definir el template permite utilizar una sintaxis básica para enriquecer el template. La mayoría de la funcionalidad aquí proviene directamente de la mencionada librería LitHTML.

Ahora vamos a ver lo más básico que podemos declarar en un template.

Volcar el contenido de una propiedad

En el template podemos interpolar el contenido de las propiedades del elemento por medio de una sintaxis básica. Todavía no hemos hablado de propiedades, pero podemos adelantar que éstas mantienen los datos que el componente debe manejar.

En nuestro componente de ejemplo vamos a construir un sencillo contador de clics. Como podrás suponer, vamos a necesitar una propiedad para llevar la cuenta de los clic realizados.

Las propiedades en LitElement se definen mediante un método "setter" estático llamado properties(), de esta manera:

static get properties() {
  return {
    clics: {
      type: Number
    }
  };
}

Es importante declarar todas las propiedades que deseamos que sean reactivas, para que se actualice automáticamente su valor en el template. Su inicialización se realiza vía constructor, de esta forma:

constructor() {
  super();
  this.clics = 0;
}

Ahora podemos volcar el contenido de esta propiedad "clics" en el template. La sintaxis para interpolar una propiedad es la misma que usamos en los template strings para interpolar una variable.

render() {
  return html`
    <h1>Componente my-counter</h1>
    <p>Clics realizados: ${this.clics}</p>
  `;
}

Como podrás imaginar, ${this.clics) se sustituirá por el valor actual de clics, que en el arranque del componente y debido al constructor, se ha definido como 0.

Para no dar pie a dudas adicionales, este es el código actual y completo de nuestro componente hasta ahora.

import { LitElement, html } from 'lit-element';

export class MyCounter extends LitElement {
  static get properties() {
    return {
      clics: {
        type: Number
      }
    };
  }

  constructor() {
    super();
    this.clics = 0;
  }

  render() {
    return html`
      <h1>Componente my-counter</h1>
      <p>Clics realizados: ${this.clics}</p>
    `;
  }
}

customElements.define('my-counter', MyCounter);

Asociar manejadores de eventos en el template

Ahora vamos a ver cómo asociar manejadores de eventos de manera declarativa dentro de nuestro template. La idea es tener un botón que, al producirse un clic sobre él, incremente la propiedad de la cuenta de clics.

Este es el código del template, donde hemos agregado el botón con su manejador de evento declarado.

render() {
  return html`
    <h1>Componente my-counter</h1>
    <p>Clics realizados: ${this.clics}</p>
    <button @click="${this.incrementarClic}">Haz click</a>
  `;
}

Como puedes ver, el evento se declara mediante una arroba "@", seguida del nombre del evento (en este claso "click") e igualando como valor la referencia al método del componente que hace las veces de manejador.

Obviamente, dentro del componente tendremos un método llamado incrementarClic() que hará la manipulación de la propiedad deseada.

incrementarClic() {
  this.clics ++;
}

Eso es todo. Ahora podemos ver el código de nuestro ejemplo completo.

import { LitElement, html } from 'lit-element';

export class MyCounter extends LitElement {
  static get properties() {
    return {
      clics: {
        type: Number
      }
    };
  }

  constructor() {
    super();
    this.clics = 0;
  }

  render() {
    return html`
      <h1>Componente my-counter</h1>
      <p>Clics realizados: ${this.clics}</p>
      <button @click="${this.incrementarClic}">Haz click</a>
    `;
  }

  incrementarClic() {
    this.clics ++;
  }
}

customElements.define('my-counter', MyCounter);
Nota: Para poner en marcha este componente en un proyecto, de modo que puedas ver cómo funciona, consulta el artículo Mi primer componente LitElement.

Buenas prácticas con templates para mantener el elevado rendimiento de LitElement

En LitElement las propiedades del componente se actualizan de manera asíncrona ante cualquier cambio, obteniendo un elevado rendimiento en muy poco peso. Pero para conseguir componentes performantes necesitamos mantener una serie de reglas a la hora de producir templates y escribir el código del componente en general.

El método render() debe ser una función pura, que dependa únicamente de las propiedades del componente. Para ello tenemos que seguir unas reglas básicas:

  • El método render no debe realizar cambios en el estado del elemento, es decir, no debe alterar sus propiedades.
  • La llamada a render no debe producir efectos laterales, limitarse únicamente a devolver el template.
  • Sólo puede depender de las propiedades del componente
  • A mismos valores de sus propiedades debe producir el mismo resultado.

Además, cuando desarrollamos el componente, debemos abstenernos de realizar cambios en el DOM directamente. En lugar de eso debemos cambiar las propiedades y dejar que el template se actualice automáticamente.

Conclusión a esta primera aproximación a los templates en LitElement

Hasta aquí tenemos un primer paso en lo que respecta a la creación de templates en LitElement. Hemos creado un template, interpolado propiedades del componente y definido manejadores de eventos, para realizar acciones cuando el usuario interacciona con nuestro template. Acciones que por cierto modifican el estado del componente, mediante la manipulación de sus propiedades, actualizándose de manera automática el template del componente.

Hasta este punto del manual hemos podido conocer lo básico de LitElement, pero quizás todavía tengas dudas sobre cómo montar tu entorno de desarrollo para empezar a trabajar y probar todos estos ejemplos. Si es el caso te recomiendo ver el siguiente vídeo de Youtube.

En el próximo artículo aprenderemos a crear estructuras condicionales y repeticiones dentro del template, lo que nos dará pie a realizar ejemplos todavía más interesantes.