ManzDev
Perfil de Manz Dashboard de actividad
HTML5 Etiquetas HTML
CSS CSS vanilla
PostCSS Transformador CSS
Javascript Javascript ES2020+
NPM Node Package Manager
WebComponents Componentes web
Terminal Terminal de GNU/Linux
VueJS Framework VueJS 3
Automatizadores Empaquetadores Javascript
Dibujando con CSS ¡Dibujos hechos con sólo CSS!

Props y hooks en Atomico

Ahora que ya hemos aprendido a crear un componente con Atomico, vamos a ver como podemos personalizarlo e indicar props (atributos HTML propios del componente) para facilitar un método de entrada de información desde el exterior al interior del componente.

Props en Atomico

En principio, estas props se facilitarán al componente por medio de atributos HTML. Nuestro componente se llama <message-component>, por lo que lo declaramos tres veces, en la primera sin ninguna prop, en la segunda con la prop number y en la tercera con la prop number y la prop data:

<message-component></message-component>
<message-component number="4"></message-component>
<message-component number="4" data="Manz"></message-component>

Luego, desde el interior de la función del componente, hay dos formas principales de capturar y utilizar esta información entrante: desestructurando parámetros y utilizando hooks. Pero antes de comenzar con esto, vamos a ver como se declaran las props del componente.

Props simples en Atomico

Para comprender como definir props en un componente, observa el siguiente ejemplo. Los pasos a seguir son los siguientes:

  1. Definir las props en la etiqueta HTML del componente (apartado anterior).
  2. Declarar una función que define el funcionamiento del componente (artículo anterior).
  3. Declarar un objeto en la propiedad .props de la función del componente (este apartado).
  4. Desestructurar parámetros o usar hooks en el componente. (siguientes apartados).

La declaración de las props del componente siempre se lleva a cabo después de haber definido la función. Esto puede chocarnos si no sabemos como funciona realmente Javascript «por debajo», al observar una asignación de la propiedad .props a una función.

En Javascript, todo son objetos (incluso las funciones), por lo que es perfectamente posible hacer lo siguiente:

// La función de nuestro componente
const messageComponent = () => {
  /* ... */
}

messageComponent.props = {
  number: Number,
  data: String
};

En el interior de este nuevo objeto declarado, tendremos varias propiedades con el nombre de las props del componente (en nuestro caso number y data) y su respectivo tipo de dato. Sin embargo, esto es sólo la modalidad básica, ya que se pueden definir como objetos y utilizar un esquema más avanzado.

Props avanzadas en Atomico

La modalidad anterior permite definir sólo la existencia y el tipo de dato de la prop, sin embargo, puede que nos interese indicar otros detalles como por ejemplo el valor por defecto que tendrá esa prop de no indicarse en la etiqueta HTML del componente u otros detalles relacionados.

Para ello, en lugar de establecer el tipo de dato, definimos un que podrá contener varias propiedades. De forma obligatoria, habrá que indicar siempre la propiedad type para el tipo de dato, no obstante, tenemos algunas más:

Opción Descripción
type Obligatorio. Tipo de dato de la prop: , , , , , ,
reflect El valor de la prop es reflejado en el atributo HTML. Soportado en los 5 primeros tipos anteriores.
attr Con reflect a true, refleja en el atributo indicado. Si no, convierte de camelCase a kebab-case.
value Valor por defecto de la prop. Usar () => ({}) para evitar referencias entre instancias.
event Despacha un evento automático cuando cambia el valor de la prop.

Así pues, podríamos ampliar nuestra prop number a esta nueva modalidad que permite especificar más datos de las props. Observa que la prop data la mantenemos de la forma simple, mientras que la prop number la hemos ampliado indicando ahora un objeto con sus características:

// La función de nuestro componente
const messageComponent = () => {
  /* ... */
}

messageComponent.props = {
  number: {
    type: Number,
    reflect: true,
    attr: "number", /* Atributo por defecto, si reflect = true */
    value: 1,
    event: {
      type: "numberChange",
      bubbles: true,
      detail: { me: "Manz" },
      composed: true
    }
  },
  data: String
};

Fíjate en la última propiedad llamada event. Es una propiedad muy interesante para enviar eventos automáticamente cuando cambie el valor de la prop en la que lo definimos. Estos eventos pueden ser escuchados mediante .addEventListener().

En dicho objeto de características, se le pueden indicar varias propiedades, entre las que se encuentran las siguientes:

Opción Descripción
type Nombre del evento a emitir cuando cambia el valor de la prop.
bubbles El evento puede ser escuchado por los padres.
detail Objeto con información adicional (detalles) del evento.
cancelable El evento puede ser cancelado.
composed Permite que el evento salga del límite del

Si tienes más dudas sobre como funciona el envío y manejo de eventos en WebComponents, tienes más información en Custom Events en WebComponents.

Desestructurar parámetros

Ahora que ya sabemos como enviar props en el custom element del componente y como definirlos en los props del componente, ahora nos queda saber como utilizarlos en el interior de la función donde declaramos el componente.

La forma más sencilla es desestructurar los parámetros de dicha función, de modo que podamos utilizar sus propiedades como si fueran variables. De forma que, en nuestro caso, quedaría algo así:

const messageComponent = ({ number, data }) => {
  /* ... */
  return html`<host shadowDom>Message #${number}. ${data}</host>`;
}

Lo que estamos haciendo en este caso, es definir un objeto anónimo (no tiene nombre) como parámetro de la función, a la vez que indicamos las propiedades que nos interesa utilizar en el interior de la función. A esta acción se le llama desestructurar un objeto, ya que hemos separado sus propiedades en variables individuales que podremos utilizar.

En Atomico si estas propiedades coinciden con el nombre que le pusimos al declarar las props, tendrán el valor correspondiente. Veamos un ejemplo completo utilizando todo lo que hemos visto en este artículo:

<message-component></message-component>
<message-component number="4"></message-component>
<message-component number="4" data="Manz"></message-component>

<script type="module">
import { c } from "https://cdn.skypack.dev/atomico";
import { html } from "https://cdn.skypack.dev/atomico/html";
import { css, useStyleSheet } from "https://cdn.skypack.dev/atomico/css";

const styles = css`
  :host {
    background: steelblue;
    color: white;
    padding: 10px;
    border: 2px solid black;
  }
`;

const messageComponent = ({ number, data }) => {
  useStyleSheet(styles);
  return html`<host shadowDom>Message #${number}. ${data}</host>`;
}

messageComponent.props = {
  number: Number,
  data: String
};

customElements.define("message-component", c(messageComponent));
</script>

En el primer caso, nos mostrará el texto «Message #.», ya que no proporcionamos props al componente y no tienen valor por defecto porque han sido definidos de la forma simple. En el segundo caso, nos mostrará el texto «Message #4.», ya que le hemos proporcionado la prop number. Por último, se nos muestra «Message #4. Manz», ya que proporcionamos las dos props: number y data.

El hook useProp

Sin embargo, aunque lo anterior suele ser suficiente, Atomico nos proporciona una forma muy interesante de trabajar con las props, debido a su enfoque funcional. Esta forma es la de utilizar Hooks.

Los Hooks son un patrón de desarrollo que ha sido popularizado en los últimos años por React. Este patrón permite simplificar y reutilizar código de una forma muy clara, utilizando un enfoque funcional, y sin necesidad de utilizar clases.

Uno de los hooks de Atomico es useProp, utilizado para obtener las props del componente. El esquema de este hook es el siguiente:

const [value, setValue] = useProp("value");

Al importar el hook (función) useProp de Atomico, este nos pedirá por parámetro el nombre del prop que vamos a gestionar. Además, devolverá un donde el primer ítem será el valor actual de la prop y el segundo valor un setter para actualizarlo.

Aunque no es absolutamente obligatorio, se suele utilizar como convención que el primer valor del array tendrá el nombre de la prop, mientras que el segundo valor tendrá el prefijo set y estará escrito en camelCase.

Veamos un ejemplo donde lo ponemos en práctica:

<message-component></message-component>
<message-component number="4"></message-component>
<message-component number="4" data="Manz"></message-component>

<script type="module">
import { c, useProp } from "https://cdn.skypack.dev/atomico";
import { html } from "https://cdn.skypack.dev/atomico/html";
import { css, useStyleSheet } from "https://cdn.skypack.dev/atomico/css";

const styles = css`
  :host {
    background: steelblue;
    color: white;
    padding: 10px;
    border: 2px solid black;
  }
`;

const messageComponent = () => {
  const [number, setNumber] = useProp("number");
  const [data] = useProp("data");
  useStyleSheet(styles);
  return html`<host shadowDom>Mensaje #${number + 1}. ${data}</host>`;
};

messageComponent.props = {
  number: { type: Number, value: 1 },
  data: { type: String, value: "Default" }
};

customElements.define("message-component", c(messageComponent));
</script>
  • En primer lugar, importamos el hook useProp del core de Atomico.
  • En segundo lugar, utilizamos useProp para las dos props del componente: number y data.
  • En tercer lugar, si queremos variar el valor de una prop, utilizaremos setNumber, como hacemos en la plantilla html, pasándole por parámetro number + 1 para aumentar su valor.

Observa que en el caso de no necesitar el setter, se puede omitir como hacemos con data y quedarnos solo con el primer valor del array. Eso sí, recuerda siempre definirlo como un array, ya que es obligatorio desestructurarlo.

El setter no sólo devolverá el valor de number + 1, sino que actualizará el valor del componente. Esto se verá más claramente en el próximo artículo donde veremos como utilizar eventos y la reactividad que proporciona el modelo de hooks de Atomico.

Manz
Publicado por Manz

Docente, divulgador informático y Streamer de código. Amante del CSS y de la plataforma web en general. Autor de Emezeta.com tiempo atrás. Ha trabajado como profesor en la Universidad de La Laguna y es director del curso de Programación web FullStack y FrontEnd de EOI en Tenerife. En sus ratos libres, busca GIF de gatos en Internet.