Componentes

En este capítulo vamos a ver todas las claves para poder entender las bases de como trabajar con los componentes en Qwik.

Una vez completados los dos primeros capítulos del libro vamos a empezar a trabajar con los componentes.

El componente es un elemento super importante para poder refactorizar y reutilizar código en nuestros proyectos.

Debido a la importancia que tiene, vamos a trabajar de manera especifica con los componentes, completamente desde 0, como si no supiéramos nada acerca de ellos.

Si habéis trabajado en otras tecnologías con los componentes, prácticamente os sonarán todos los conceptos y los podréis usar como repaso adaptando la sintaxis a Qwik, algo que si debemos de tener en cuenta.

A tener en cuenta

Aunque este tema es muy muy básico, conviene hacer un pequeño repaso, para ir paso a paso asentando las bases e ir aumentando la complejidad de lo que es el temario del libro.

Hay que tener en cuenta que estos capítulos son detallados pensando en todos los niveles, para animar a más gente a trabajar con este framework, por lo que si tenéis experiencia y algunos aspectos os resultan “absurdos” paciencia por favor.

Contenido del capítulo

Esto es lo que vamos a ver en este capítulo:

Quizás os suenen todos los puntos, alguno o ninguno.

Independientemente de si os suenan o no, vamos a ver todos con una explicación y sus casos de uso, con varios ejemplos, para poder ir asimilando los conceptos y así poder trabajar en los siguientes puntos.

Antes de empezar a trabajar

Para empezar con los aspectos teórico-prácticos de los componentes, debemos de crear un proyecto nuevo tal y como se ha explicado anteriormente y se recomienda llamar al directorio 03-components para ir implementando el progreso paso a paso.

¿Qué es un componente?

Los componentes son las piezas del puzzle básicas que se forman con una función dentro de una aplicación Qwik que se declaran mediante component$() y, como mínimo, deben devolver un elemento JSX.

Componentes - Más detalles

Si queréis ver más detalles y profundizar sobre los componentes os añado la referencia oficial de la Documentación de Qwik sobre los componentes: https://shorten-up.vercel.app/RItZpVaiE4

Podremos tener componentes dentro de otros componentes y pasar información a través de ellos, para personalizar su contenido y toda su lógica en base a nuestras necesidades.

Sabiendo ya que es un componente, comencemos trabajando con los diferentes apartados, desde la creación del componente hasta aplicar estilos.

Crear componente básico

Para crear un componente básico de Qwik, debemos de importar la siguiente declaración desde el paquete de qwik en el fichero donde vamos a trabajar (Por ejemplo yo trabajaré en un nuevo proyecto en el fichero inicial: src/routes/index.tsx)

import { component$ } from '@builder.io/qwik';

Y creamos el componente con esta estructura:

export default component$(() => {
  return <>
    AÑADIR TODO EL CONTENIDO con notación JSX
  </>;
});

Y si ahora queremos crear un componente enviando un saludo como Hola Qwik, espero aprender mucho sobre ti :)

import { component$ } from '@builder.io/qwik';

export default component$(() => {
  return <>Hola Qwik espero aprender mucho sobre ti :)</>;
});

Y se verá de la siguiente forma:

Si cambiamos el contenido con una nueva cadena de texto:

import { component$ } from '@builder.io/qwik';

export default component$(() => {
  return <>Hola Qwik, soy Anartz y espero aprender mucho sobre ti :)</>;
});

El resultado se refleja de la siguiente forma:

Binding Expressions: Inyectar información en plantilla

El propósito de los componentes es fusionar datos con la plantilla JSX.

Usamos la {expresión} para inyectar datos en un componente dentro del código. Las expresiones se colocan como un nodo de texto o como un atributo en un elemento.

Imaginaros que empezamos con esta información que queremos inyectar en la plantilla:

const data = {
    href: 'https://mugan86.medium.com/',       // se añade como atributo
    text: 'Anartz Mugika - Tech Blog'          // Información que se muestra
};

Teniendo esta información, vamos a inyectarla dentro de un elemento <a></a> para añadir enlaces:

Lo añadiremos de la siguiente forma, teniendo en cuenta que la constante data tiene como valores lo siguiente:

import { component$ } from '@builder.io/qwik';

export default component$(() => {
  const data = {
    href: 'https://mugan86.medium.com/',
    text: 'Anartz Mugika - Tech Blog'
  };
  return (
    <>
      <h1>{ data.text }</h1>
      <br />
      <a href={ data.href } target={ '_blank' }>{ data.text }</a>
    </>
  );
});

El resultado que se obtiene es lo siguiente:

Inyectar información de manera condicional

También podemos renderizar e inyectar información de manera condicional y aparte de iterar estructuras repetitivas.

Lo aplicamos siguiendo esta definición:

{  ( CONDICION_A_EVALUAR ) ? 'SE CUMPLE': ' NO SE CUMPLE'   }

Adaptándolo al código, tendremos dos valores iniciales y vamos a comprobar la condición de age, para ver si cumple o no.

Fijaros que en el punto donde NO se cumple he puesto 3 años.... Con esto quiero decir que tenemos la opción de ejecutar operaciones matemáticas, concatenaciones de dos valores ó más...

El componente se construirá de la siguiente forma:

export default component$(() => {
  const age = 37;
  const name = 'Anartz';
  return (
    <>
      <h1>{ name }</h1>
      <br />
      <p>
        {
          ( age > 40 ) ? 
          'Como "tienes más de 40 años", tienes 18 años y más de 22 años de experiencia ;)' : 
          `Eres joven, todavía tienes ${ age } años y te faltan ${ 40 - age } para volver a cumplir 18 años ;)`
        }
      </p>
    </>
  );
});

Y su resultado será el siguiente, que como el valor de la edad NO CUMPLE muestra este mensaje:

Si cambiamos el valor de age por uno superior a 40 muestra el siguiente mensaje, ya que SI CUMPLE la condición:

Inyectar información mediante el uso de estructuras repetitivas

También podemos renderizar estructuras de tipo bucle dentro como por ejemplo esta lista de hobbies:

<ul>
  { 
    ['leer', 'deporte', 'videojuegos' ].map(
          (value, index) => (<li>{index + 1} - {value}</li>)
    )
  }
</ul>

Añadiendo este código al anterior desarrollado:

export default component$(() => {
  const age = 56;
  const name = 'Anartz';
  return (
    <>
      <h1>{name}</h1>
      <br />
      <p>
        {age > 40
          ? 'Como "tienes más de 40 años", tienes 18 años y más de 22 años de experiencia ;)'
          : `Eres joven, todavía tienes ${age} años y te faltan ${
              40 - age
            } para volver a cumplir 18 años ;)`}
      </p>
      <ul>
        {['leer', 'deporte', 'videojuegos'].map((value, index) => (
          <li key={index + '_hobby'}>
            {index + 1} - {value}
          </li>
        ))}
      </ul>
    </>
  );
});

El resultado será el siguiente:

  1. Título en el que inyectamos el valor name.
  2. Texto condicional que SI cumple la condición ya que age es mayor que 40 siendo su valor 56.
  3. Iteración en lista de los 3 hobbies añadidos donde tenemos que añadir el valor key ¿Para qué sirve este valor? Tal y como se hace en React, usamos key como ayuda para identificar que elementos han cambiado, han sido agregados, o eliminados.

Si vamos a inspeccionar el código mediante las Herramientas del Desarrollador podremos ver como se añaden esos valores key elemento a elemento.

En este caso, deberían de ser elementos con los siguientes valores key:

Mirando en las Herramientas de desarrollador:

Ahora sabiendo ya estos conceptos básicos, vamos a trabajar con los componentes en trozos de código más pequeños para poder aplicar mejores prácticas y delegar las responsabilidades de mejor manera.

Componentes con componentes hijos / Inline components

Los componentes se componen de otros componentes para crear las aplicaciones y lo recomendable es que se haga uso de ellos, sobre todo pensando en reutilizarlos para usos similares, como podrían ser los botones de acción, listas, elementos tipo select,…

Uno de los superpoderes de Qwik radica en sus características de carga diferida (lazy-loading) cuyas características podemos resumirlas de la siguiente manera:

El símbolo $ marcará un cierre como de carga diferida. Por ejemplo, el método component$() hace que el componente se comporte cargándose de forma diferida.

$ - Documentación oficial

Si queréis ver más detalles y profundizar sobre la función os añado la referencia oficial de la Documentación de Qwik sobre ello: https://shorten-up.vercel.app/QHfEoX7qmE

Cuando veamos un $ en el código Qwik, estaremos cruzando un límite de carga diferida y deberemos de tener en cuenta las reglas especiales:

Si queremos asegurarnos de que un componente se carga con otro componente de manera diferida, debemos de crear un componente en línea.

Los componentes en línea se cargan como parte del componente principal y son equivalentes a como la mayoría de los otros frameworks tratan con los componentes sin usar el método component$.

Siguiendo el ejemplo que tenemos, podemos separar ese código en tres componentes en línea:

Haciendo la separación con el objetivo de optimizarlo, lo dejamos de la siguiente manera:

export const Title = () => {
  const name = 'Anartz';
  return <h1>{name}</h1>;
};

export const Age = () => {
  const age = 37;
  return (
    <p>
      {age > 40
        ? 'Como "tienes más de 40 años", tienes 18 años y más de 22 años de experiencia ;)'
        : `Eres joven, todavía tienes ${age} años y te faltan ${
            40 - age
          } para volver a cumplir 18 años ;)`}
    </p>
  );
};

export const Hobbies = () => {
  return (
    <ul>
      {['leer', 'deporte', 'videojuegos'].map((value, index) => (
        <li key={index + '_hobby'}>
          {index + 1} - {value}
        </li>
      ))}
    </ul>
  );
};

// Componente principal
export default component$(() => {
  return (
    <>
      <Title />
      <br />
      <Age />
      <Hobbies />
    </>
  );
});

Si accederemos al navegador, seguiremos viendo lo mismo sabiendo que ya lo tenemos en 3 partes principales:

Eso si, la estructura HTML ha cambiado de lo que teníamos antes que era lo siguiente:

A esto, donde ya se pueden ver mediante un valor key los diferentes componentes renderizados, empezando desde RS_0 hasta RS_2, que corresponde a los componentes <Title />, <Age /> y <Hobbies />:

Llegados a este punto, podemos dar por finalizado el apartado de añadir componentes dentro de otros componentes y ahora vamos al siguiente punto que será el último antes de hacer una introducción a añadir estilo, para poder trabajar con los props.

Props: Pasando datos a componentes

Las aplicaciones web se construyen a partir de componentes de la misma manera que las aplicaciones generales se construyen a partir de funciones.

La composición de funciones no sería muy útil si no se pudieran pasar parámetros. De la misma manera que las funciones tienen parámetros, los componentes tienen props.

¿Qué es un prop?

Cuando hablamos de prop hará referencia a las propiedades las cuales cumplen un rol importante en el proceso de desarrollo de una aplicación o página web constituido por un componente o un número concreto de componentes

Un componente usa props para pasar datos a sus componentes secundarios (hijos) en los cuales vamos a recibir esa información y la vamos a inyectar en el componente hijo e incluso, si hubiesen hijos en este componente, pasar la información a estos también hasta llegar a un punto final.

Modificamos el componente <Title /> y añadimos la opción para recoger información mediante props.

Ahora mismo usamos la constante name, pero vamos a añadirle también un tipo de saludo, así veremos que podemos pasarle uno ó más props sin problemas.

Especificamos con una interface los props de <Title />:

interface TitleProps {
  greetingText: string;
  name: string;
}

Modificamos el componente <Title />, añadiendo en el apartado de parámetros, para que obtenga un objeto con los props especificados en la interface e inyectamos los valores, eliminando la constante con el valor Anartz.

Así vamos a conseguir más dinamismo:

interface TitleProps {
  greetingText: string;
  name: string;
}
export const Title = ({greetingText, name}: TitleProps) => {
  return <h1>{greetingText} {name}</h1>;
};

Una vez almacenados los cambios, nos aparece un error y esto es debido a que hemos especificado en la interface que las dos propiedades de TitleProps son obligatorias y en este momento no estamos pasando nada:

Por lo tanto, tenemos que pasar los valores desde el componente padre al hijo, de esta manera, inyectando el valor en la propiedad especificada, en este caso sería con:

  • greetingText: 'Hola'
  • name: 'Anartz'

Quedando de la siguiente forma:

export default component$(() => {
  return (
    <>
      <Title greetingText={'Hola'} name={'Anartz'}/>
      <br />
      <Age />
      <Hobbies />
    </>
  );
});

Y el resultado debería de ser como el siguiente después de aparecer el error:

Podemos reutilizar ese componente añadiendo otro título al final, después del componente <Hobbies />:

export default component$(() => {
  return (
    <>
      <Title greetingText={'Hola'} name={'Anartz'}/>
      ...
      <Title greetingText={'Adios'} name={'Qwik'}/>
    </>
  );
});

Quedando de la siguiente forma:

Tarea propuesta

Modificad el código para que en el componente principal podamos pasarle la edad (age) al componente Age y enviaremos los hobbies a Hobbies, todo esto mediante props que en el primer caso podríamos llamar AgeProps y en el segundo HobbiesProps. Os pido que lo intentéis, no es difícil y así podemos practicar lo visto.

El resultado os lo dejo aquí:
https://shorten-up.vercel.app/uOzuP6vdyz

El resultado visual es el mismo, pero evidentemente mucho mejor y más personalizable, con lo que estaremos aplicando mejores prácticas.

Reutilización de los componentes

Si ahora quisiéramos utilizar estos componentes en el futuro en otras páginas, lo ideal sería que traslademos a ficheros independientes su contenido.

Esto lo hacemos tal cual como lo teníamos dentro del fichero index.tsx del directorio src/routes usando una nueva carpeta en src/components estructurado de la siguiente forma:

src/
  └── components/
          |── title/
          |     └── index.tsx
          |── age/
          |     └── index.tsx
          └── hobbies/
                └── index.tsx

Y después de la refactorización, el fichero index.tsx de src/routes quedará así:

import { component$ } from '@builder.io/qwik';
import { Age } from '~/components/age';
import { Hobbies } from '~/components/hobbies';
import { Title } from '~/components/title';

export default component$(() => {
  return (
    <div>
      <Title greetingText={'Hola'} name={'Anartz'} />
      <br />
      <Age age={37} />
      <Hobbies hobbies={['leer', 'deporte', 'videojuegos']} />
      <Title greetingText={'Adios'} name={'Qwik'} />
    </div>
  );
});

Visualmente seguiremos teniendo lo mismo. Lo que se mejora es la organización y reutilización del código pensando en futuras implementaciones

Estilos

Los estilos son una parte importante del diseño de una aplicación web, aunque muchas veces se infravaloren son muy importantes para dar un toque personalizado a nuestros proyectos que pueden marcan la diferencia.

Qwik es responsable de cargar la información de estilo cuando se monta un componente.

Los añadimos ahora mismo en línea:

...
export const Title = ({greetingText, name}: TitleProps) => {
  return (
    <h1 style="color:blue;font-size:46px;">
      {greetingText} {name}
    </h1>
  );
};

Y aunque se visualizan los cambios correctamente:

De esta manera funciona, pero no es la correcta en Qwik, en uno de los próximos capítulos, cuando hablemos de los estilos, trabajaremos con funciones tipo hook como useStyles$() y useStylesScoped$() para aplicar buenas prácticas.

Ahora en este momento lo vamos a dejar así, como base inicial enfocado a los componentes.

Para separarlos con colores y así verlos visualmente como tenemos estructurado este componente principal (padre) con los tres componentes hijos o complementarios.

Vamos a añadirle un borde al componente principal (padre) y a los hijos, de esta manera:

Title

export const Title = ({ greetingText, name }: TitleProps) => {
  return (
    <div style='border:2px solid red; padding: 5px'>
      <h1 style='color:blue;font-size:46px;'>
        {greetingText} {name}
      </h1>
    </div>
  );
};

Age

export const Age = ({ age }: AgeProps) => {
  return (
    <div style='border:2.5px solid yellow; padding: 5px'>
      <p>
        ...
      </p>
    </div>
  );
};

Hobbies

export const Hobbies = ({ hobbiesList }: HobbiesProps) => {
  return (
    <div style='border:4px solid green; padding: 5px'>
      <ul>
        ...
      </ul>
    </div>
  );
};

Componente principal

export default component$(() => {
  return (
    <div style='border: 3px dotted purple; padding: 10px'>
      ...
    </div>
  );
});

Cuyo resultado será el siguiente:

Resultado de lo trabajado en este capítulo

El código que encontráis es el resultado final de todo el proceso realizado durante el capítulo. Os recomiendo que vayáis haciendo los pasos poco a poco y paso a paso para ir interiorizando todos los conceptos.

El enlace lo tenéis a continuación:
https://shorten-up.vercel.app/Yt95TtHeNw

¿Qué hemos aprendido en este capítulo?

Al finalizar este capítulo, deberíamos de ser capaces de:

Conclusión

Hemos trabajado con un concepto muy básico (y a la vez importante) como el uso de los componentes.

Hemos empezado desde lo más básico hasta dejar todo ya bien definido con propiedades y estilos, con diferentes casos en los que funcionará todo bien y aplicando las mejores prácticas posibles.

Teniendo los conocimientos básicos, enrutamiento (Routing) y componentes podemos pasar al siguiente capítulo donde trabajaremos con las plantillas, haciendo uso principalmente de componentes y diferentes modos de enrutamiento. ¡¡Nos vendrá perfecto para repasar!!