> Manuales > Manual de Lit

Qué son las propiedades de los componentes en Lit, qué nos ofrecen en el desarrollo de los custom elements. Veremos los conocimientos más importantes que debes conocer para trabajar con propiedades en Lit.

Propiedades de los componentes Lit

En el anterior artículo dimos los primeros pasos con Lit, abordando la creación de componentes, basados en el estándar de Web Components, usando la librería Lit. Ahora vamos a centrarnos en el uso de las propiedades de los componentes.

¿Qué son las propiedades?

Las propiedades son una de las principales herramientas que tenemos a la hora de desarrollar los componentes. Cumplen varias funciones, entre las que podemos destacar:

Por tanto, usaremos propiedades siempre que necesitemos tratar con datos en el componente y beneficiarnos de las funcionalidades que aporta Lit. Gracias a las propiedades los templates podrán reaccionar automáticamente cuando sus valores cambian. También serán necesarias las propiedades cuando tenemos datos que queremos que se puedan asignar mediante el HTML del componente.

Cómo definir propiedades en los componentes

Podemos definir propiedades por medio de decoradores o mediante la propiedad static properties.

Como los decoradores no forman todavía parte de Javascript (es una propuesta para incluir más adelante en el estándar en el momento de escribir este artículo) tendríamos que compilar los componentes con Babel o usar TypeScript. Por ello vamos a preferir, al menos en este manual, el uso de propiedades declaradas mediante "static properties".

static properties = {
    message: { type: String },
}

Otra sintaxis similar que también podemos usar es mediante un getter. No cambia el funcionamiento del componente que lo hagas de una manera u otra.

static get properties() {
    return {
        tags: { type: Array },
    };
}

Como puedes ver, las propiedades se declaran mediante un objeto. Cada una de las propiedades del objeto sería una propiedad del componente.

Existen diversas configuraciones para las propiedades, pero generalmente indicaremos como mínimo su tipo, entre uno de estos posibles: String, Number, Boolean, Array, Object.

Inicializar propiedades

Las propiedades las inicializamos en el constructor de la clase del componente. Simplemente usamos "this" para acceder a ellas.

constructor() {
    super();
    this.message = "Hola Lit!!!"
}

Es fundamental que no te olvides de invocar al constructor de la clase padre, con super(), antes de inicializar las propiedades o hacer cualquier otra tarea en el constructor.

Bindear propiedades en los templates

Dentro de los templates somos capaces de bindear el valor de las propiedades, para que aparezca en la vista del componente. El bindeo lo hacemos mediante la interpolación de valores en el Tagged Template String, con el código nativo de Javascript que usamos para interpolar valores en las cadenas.

render() {
    return html`<div>${this.message}</div>`;
}

De este modo, cada vez que el valor de la propiedad cambia, se realizará una actualización del template y por lo tanto cambiará la vista del componente. Esto lo hará Lit automáticamente, sin que tengamos que intervenir imperativamente.

Propiedades vs atributos y sincronización

Las propiedades ya sabemos lo que son: lugares donde se almacenan los datos con los que trabajamos en los componentes. El concepto de atributo se refiere lo que encontramos en las etiquetas HTML que escribimos cuando usamos los componentes.

<mi-componente miatributo="123"></mi-componente>

En esa etiqueta "miatributo" es un atributo. Generalmente en el componente existirá una relación entre el atributo con una propiedad declarada, pero no tiene por qué ser así. Es decir, puedes usar atributos que realmente no se correspondan con propiedades.

En todo caso, Lit permite que los valores de los atributos de la etiqueta del componente se sincronicen con las propiedades declaradas en el propio componente. Esto quiere decir que, cuando cambia el valor del atributo, Lit se encarga de cambiar el valor de la propiedad dentro del componente.

Por lo tanto, podría tener un componente usado de esta manera:

<dw-message message="Me gusta Lit"></dw-message>

Eso querría decir que el valor de la propiedad "message" con valor "Me gusta Lit" se asignará como valor a la propiedad del componente, sobreescribiendo cualquier inicialización que se haya realizado en el constructor.

Primero se ejecutará el constructor, almacenando el valor de inicialización en la propiedad. Por supuesto, si es que existe constructor, porque podría no existir, en cuyo caso la propiedad nacería con un "undefined". Luego Lit hace el trabajo de sincronización del valor del atributo al valor de la propiedad, cambiando la inicialización del constructor, si es que había.

Además, cuando por lo que sea cambie el atributo de la etiqueta host, Lit se encargará de trasladar ese valor a la propiedad y, por tanto, actualizar el template del componente.

Propiedades y "class fields"

Apuntamos un detalle interesante acerca de las propiedades del componente y las propiedades de la clase o "class fields" que merece la aclaración.

Como "class fields" nos referimos a los atributos de una clase (class de programación orientada a objetos).

class ClaseConClassField {
  // no uses propiedades de clase si éstas quieres que sean reactivas
  soyClassField = 'propiedad de clase';
}

Este tipo de propiedades han llegado hace poco al estándar Javascript y desde hace poco tienen soporte en la mayoría de los navegadores.

En TypeScript existen los class fields desde siempre y en Javascript podrías hacer uso de ellas de manera segura mediante la compilación con Babel.

El caso es que este tipo de propiedades tienen precedencia en relación a las propiedades declaradas en el componente, por lo que si las usas en tu componente no serán reactivas, aunque las declares en el objeto de properties.

Técnicamente ocurre porque las propiedades se generan como accessors en la clase. Los "class fields" son prioritarios con respecto a los accessors.

En cambio, no hay problema con crear clases que tengan atributos de la manera tradicional, inicializandolos en los objetos instanciados al vuelo en el constructor, de este modo.

constructor() {
  super();
  this.propiedadDeClase = "Esto no tiene problema";
}

En TypeScript se pueden usar class fields, porque usamos decoradores que hacen el trabajo de convertirlos a propiedades del componente. No los vemos aquí porque, al menos de momento, vamos a limitarnos a poner ejemplos en Javascript, pero podrías consultar la documentación para más información.

Opciones de las propiedades

A continuación vamos a repasar las opciones que puedes usar a la hora de declarar propiedades, a modo de referencia. Iremos viendo ejemplos a lo largo del manual.

attribute

Define cuándo una propiedad se debe asociar con un atributo, y por lo tanto sincronizar su valor con el atributo. Es un valor boleano que por defecto es true. Si fuera false, un posible atributo en la etiqueta host no modificaría el valor de la propiedad en el componente.

converter

Permite crear un conversor entre el valor del atributo y la propiedad. Existe un conversor predeterminado que depende del tipo de la propiedad que se haya configurado en la declaración ```properties```.

hasChanged

Se puede indicar una función que se usará para determinar si la propiedad ha sufrido cambios. Solo cuando sufre cambios una propiedad se dispara una actualización del template del componente.

Por defecto se comprueban los cambios con el operador que determina la igualdad y el tipo (nuevoValor !== antiguoValor).

noAccessor

Esta opción permite no crear los accessors a las propiedades. Los accessors son los getter y los setters de la propiedad que genera LitElement de manera predeterminada cuando se declaran las propiedades en el objeto properties.

Solamente tendría sentido usar esta opción si has creado tus propios accessors, lo que sería bastante poco común.

reflect

Esta opción hace que, en el momento que se actualiza la propiedad en el componente, se actualice también el atributo en la etiqueta host del componente. Se setea con un valor boleano y de manera predeterminada es false.

state

La opción state es para definir una propiedad que se comporte como estado del componente. Esto tendría algunas connotaciones interesantes que hay que explicar.

El estado del componente en Lit se refiere a datos que existen en el componente y que son reactivos en el template, pero que no forman parte del API del componente, por lo que serían considerados como propiedades privadas, que solamente se pueden manipular desde dentro del componente.

Esto quiere decir que Lit no creará un atributo para el componente con las propiedades marcadas como "state", por lo que no se podrán setear desde fuera, en la etiqueta host. Por defecto es false, lo que quiere decir que normalmente las propiedades forman parte del API del componente, por tanto son públicas y se podrá usar un atributo para actualzarlas.

type

Este es el tipo de la propiedad, que puede ser uno de los tipos primitivos de Javascript (String, Number, Boolean) así como Object y Array.

Este tipo no está marcando que la propiedad tenga un tipo de datos determinado de manera obligada y no se pueda cambiar. Recordemos que Javascript continúa siendo un lenguaje levemente tipado, por lo que podríamos en verdad asignar cualquier tipo de datos a una propiedad independientemente de lo que hayamos dicho en la opción "type". En realidad esta configuración sirve porque, dependiendo del tipo, puede haber unos conversores predefinidos que se ejecutan cuando el valor de la propiedad viene fijado desde los atributos del componente.

El valor predeterminado de la opción type sería String.

Conclusión

Hemos aprendido lo más importante de las propiedades de los componentes con Lit. Serán básicas en prácticamente todos los componentes que vayas a realizar a lo largo del desarrollo de custom elements, por lo que las usaremos constantemente durante todo el manual.

Ya para acabar te dejamos el código de un componente completo, el mismo que implementamos en el ejercicio anterior, que mostraba un mensaje. En este caso incorporamos la propiedad "message", que está bindeada al template.

import {LitElement, html, css} from 'lit';

class DwMessage extends LitElement {

    static styles = css`
        :host {
            display: inline-block;
            border-radius: 0.5rem;
            padding: 0.2rem 0.5rem;
            background-color: #475119;
            color: #fff;
            font-weight: bold;
        }
    `;

    static properties = {
        message: { type: String },
    }

    constructor() {
        super();
        this.message = "Hola Lit!!!"
    }

    render() {
        return html`<div>${this.message}</div>`;
    }
}

customElements.define('dw-message', DwMessage);

Ahora podrás usar el componente enviando cualquier mensaje personalizado por medio del atributo "message" de la etiqueta host.

<dw-message message="Ya puedo cambiar el texto de este mensaje mediante el atributo"></dw-message>

Si quieres ver el código que hemos realizado hasta el momento lo tienes en GitHub.

Miguel Angel Alvarez

Fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Com...

Manual