Routing: Sistema de enrutamiento con QwikCity

Comenzamos un nuevo capítulo, después de haber realizado la introducción a las características de Qwik y sus diferencias con otras tecnologías Javascript.

En el anterior capítulo nos hemos quedado en el punto en el que hemos creado nuestro primer proyecto donde se ha implementado el enrutamiento (Routing) a un nivel muy básico con un par de rutas.

Lo que vamos a ver en este capítulo es una continuación de lo visto con anterioridad con Qwik City, donde lo veremos con todo detalle, tratando todos los aspectos más importantes y necesarios para poder trabajar de diferentes formas con ello.

Contenido del capítulo

Esto será lo que veremos en este capítulo junto con los conceptos que vamos a aprender.

¿Qué es Routing?

El enrutamiento (Routing) es una forma de asignar direcciones URL públicas de un sitio a componentes específicos declarados en nuestra aplicación.

En Qwik tenemos Qwik City, que utiliza el enrutamiento basado en directorios.

Información sobre Qwik City

Información con ejemplos y más detalles sobre Qwik City.

Enlace a la documentación:
https://shorten-up.vercel.app/4vwjQzLolW

¿Qué significa enrutamiento basados en directorios?

La estructura de su directorio de rutas va a especificar las URL públicas que el usuario va a poder hacer uso en su aplicación.

Una vez que ya nos hemos sumergido en este apartado tan importante y fantástico a la vez, vamos paso por paso, viendo los diferentes apartados.

Enrutamiento basado en directorios

Crear el proyecto para trabajar en el capítulo

Antes de empezar a trabajar nuestro proyecto, dejamos de lado el proyecto inicial 01-first-steps y creamos uno nuevo, siguiendo los pasos expuestos en el capítulo anterior. Como sugerencia os animo a que lo llaméis 02-routing.

El resultado paso a paso os lo iré dando con las diferentes variantes que iré creando a lo largo del capítulo como 02-01-routing-basic, 02-02-routing-markdown, etc.

Con este paso que hemos dado, ya tenemos preparado el proyecto sin ninguna configuración para poder ir viendo el progreso paso a paso.

En el directorio de rutas de Qwik City (src/routes), añadimos una nueva carpeta que será la carpeta-ruta-principal-de-la-ruta + un fichero index que en conjunto harán que se asignen a una ruta URL de manera automática.

Por ejemplo, imaginaros que vemos una ruta con esta apariencia:

Sabemos que esta ruta se mostrará con el contenido del componente exportado en src/routes/admin/index (ya sea .mdx o .tsx, entre otros formatos, que comentaré a continuación de los que podemos hacer uso).

src/
  └── routes/
      └── admin/
          └── index.tsx  # (.mdx, .jsx, ...) # /admin

Hay que tener en cuenta que el archivo del final de la ruta se debe de llamar index. Esto es SUPER importante.

Quizás en otros frameworks con los que habéis trabajado, se hace una distinción entre páginas y endpoints de datos o rutas API.

En Qwik City no hay distinción, todos son endpoints en el fichero index.[tipo-de-fichero].

Podríamos añadir ficheros con otros nombres, pero para acceder a ellos como endpoints, NO van a servir.

Tipos de archivo permitidos para definir las rutas

Qwik City admitirá los siguientes tipos de archivos para rutas, lo que si tenemos que hacer es SIEMPRE llamar al fichero index, como se ha mencionado.

Nosotros podemos definir el fichero index.[tipo-de-fichero] donde [tipo-de-fichero] puede ser una de las siguientes extensiones:

  • .ts
  • .tsx
  • .js
  • .jsx
  • .md
  • .mdx

Funcionamiento con primeras páginas

Para ver el funcionamiento de los nuevos endpoints vamos a ver un ejemplo con un caso QUE NO FUNCIONA en Qwik (por no estar creando el directorio con su fichero index dentro) pero que probablemente funcione en otros frameworks o tecnologías con las que habéis trabajado, pero en Qwik no.

Creamos los siguientes ficheros y en todos añadimos el contenido del componente por defecto como este contenido en cada uno de los 4 ficheros (src/routes/index.tsx no lo tocamos):

Siendo esta la estructura de los ficheros en estos momentos:

src/
  └── routes/
      ├── contact.tsx
      ├── blog.tsx
      ├── portfolio.tsx
      ├── layout.tsx
      └── index.tsx

Si hacemos la prueba con los siguientes endpoints teniendo como base la URL local, que en mi caso es http://127.0.0.1:5173.

  • index.tsx (http://127.0.0.1:5173): FUNCIONA.
  • portfolio.tsx (http://127.0.0.1:5173/portfolio): NO FUNCIONA

En este caso, como NO encuentra este recurso nos devuelve un error 404 con un mensaje general como el que se puede ver en la captura anterior.

Sobre el tema de personalizar esta página con el error 404 lo realizaremos en el capítulo correspondiente a cuando estemos trabajando con el apartado de layouts (plantillas).

Si probamos con blog.tsx (http://127.0.0.1:5173/blog) y con contact.tsx (http://127.0.0.1:5173/contact) nos pasará como con portfolio, que NO encontrará la ruta y nos devuelve un error 404.

SOLO funcionará con index.tsx que cumple correctamente la nomenclatura del nombre de fichero, dentro de routes en la raíz de esta carpeta, por lo que como se ha podido ver, accede al contenido.

Por lo tanto, para que el acceso a una ruta SI funcione en nuestro proyecto Qwik debemos de dejar el contenido estructurado de la siguiente forma, con el directorio especificado para la página y dentro un fichero index.tsx (Recordad las diferentes extensiones disponibles):

src/
  └── routes/
      ├── portfolio/
      │   └── index.tsx # /portfolio
      ├── contact/
      │   └── index.tsx # /contact
      ├── blog/
      │   └── index.tsx # /blog
      └── index.tsx     # /

Comprobando las rutas que nos han dado problemas:

  • /blog

Haremos lo mismo con /portfolio y /contact, nos debería de aparecer el contenido concreto a esas rutas, comprobadlo.

Puede pasar lo siguiente, que al ir a cargar cualquiera de las rutas en las que hemos visualizado el error “404 Not found” se muestre el contenido de la página en blanco, cosa que no debería:

Y en la consola un mensaje similar al siguiente:

Si ocurre esto, debemos de parar la ejecución del servidor y reiniciarlo. Volved a probar.

Al principio, el estructurar así los endpoints, puede parecer un trabajo extra, pero este enfoque tiene sus ventajas.

Una de esas ventajas es poder definir archivos de componentes en un directorio de ruta sin que se representen, pudiendo usarlo como un componente complementario para esa ruta que se añade al componente principal representado en index.

Se podría considerar de la siguiente manera:

  src/
  └── routes/
      ├── index.tsx                   # /
      ...
      └── blog/
          ├──  index.tsx              # /blog
          └──  other-component.tsx    # Ignorado y NO mapea en URLs.

El fichero other-component.tsx puede ser importado y usado dentro src/routes/blog/index.tsx y la vez será completamente ignorado por Qwik City a la hora de enrutar.

Si ejecutamos en el navegador la ruta http://127.0.0.1:5173/blog/other-component nos dará un error 404.

Alternativa para evitar ese error

Otra cosa es que decidamos añadir una nueva carpeta llamada other-component junto con el fichero index.tsx correspondiente dentro de src/routes/blog. En ese caso esa ruta si estaría disponible.

Una vez que tenemos claro como se definen las rutas, vamos a trabajar en lo que son los contenidos de esos endpoints.

Añadiendo el contenido de los endpoints

El contenido de index, de los puntos de entrada (endpoints), podremos implementarlo en formato componente o mediante el uso del renderizado de páginas con contenido markdown (extensiones .md o .mdx).

Nos vamos a centrar en cada uno de ellos para ver diferentes opciones y así saber cual nos puede interesar en algunos casos u otros, para llevar a cabo nuestros proyectos personales y profesionales.

Implementando un componente

Para devolver HTML para una ruta específica, deberá implementar un componente. Para archivos .tsx, el componente debe exportarse por defecto que llamaremos default.

De manera alternativa, podremos renderizar contenido en formato Markdown con las extensiones .md y .mdx.

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

export default component$(() => {
  return <H1>Hello World!</H1>;
});

Implementando contenido en formato Markdown

Para poder trabajar con rutas que visualizan contenido en formato Markdown, lo que es el modo de crear el path (endpoint), es el mismo procedimiento, creando la carpeta.

Lo único que va a cambiar es la extensión fichero que crearemos, que en vez de llamarse index.tsx como hemos estado haciendo hasta este momento, será index.mdx (o index.md)

Añadimos una nueva ruta llamada /readme junto con su fichero index, que en este caso será con la extensión mdx dentro de src/routes/readme/index.mdx (o md).

  src/
  └── routes/
      ├── index.tsx            # /
      ...
      |── readme/
      |     └───  index.mdx    # /readme
      └── blog/
            └───  index.tsx    # /blog

Diferencias entre usar .md ó .mdx

En la primera introduciremos contenido estático y en el segundo nos da opción de darle vida utilizando componentes Qwik (como cuando trabajamos con React)

Una vez creado el fichero, añadimos el siguiente contenido:

Ahora si accedemos al endpoint asignado: http://127.0.0.1:5173/readme.

Debería de mostrarse la información (recordad, que habrá que reiniciar para que se asigne el endpoint en el caso de no visualizarse el contenido):

Si añadimos un elemento de lista con un enlace:

# Readme Rutas

Trabajando con formato markdown
* [Twitter](https://twitter.com/mugan86)

Al recargar quedará de la siguiente forma:

Podemos finalizar este apartado haciendo uso de la característica de los ficheros mdx añadiendo un componente como contenido que crearemos en este directorio.

Creamos el componente con el fichero hello.tsx en src/routes/readme, añadiendo este contenido:

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

export default component$(() => (
  <>
    Hello using <b>.mdx</b> type file
  </>
));

Ahora estando en el fichero index.mdx de este directorio, usando el siguiente contenido:

import HelloReadme from './hello.tsx';
... (AQUÍ LO QUE TENÍAMOS HASTA AHORA)
<HelloReadme />

Lo añadimos al fichero .mdx, dejando de la siguiente forma:

import HelloReadme from './hello.tsx';

# Readme Rutas

Trabajando con formato markdown
* [Twitter](https://twitter.com/mugan86)

Este es un ejemplo usando un componente de Qwik dentro de un fichero `mdx`
<HelloReadme />

Y este sería el resultado final dentro de la ruta asociada a la carpeta readme que se encuentra en src/routes/readme cuya ruta pública es: http://127.0.0.1:5173/readme.

Y llegados a este punto ya sabemos como trabajar con los endpoints principales, gestionando los contenidos en diferentes formatos.

Resultado de lo trabajado hasta aquí

Abriendo el proyecto 02-01-routing-basic aquí:

  • Tenemos 3 nuevos endpoints nuevos que renderizan ficheros de extensión *.tsx.
  • Un nuevo endpoint que renderiza su contenido a partir de un fichero Markdown, con la extensións que renderizan ficheros de extensión *.mdx.

Routing Avanzado — Enrutamiento mediante parámetros

Los parámetros de ruta son partes de las URL que se extraen en los parámetros.

Imaginaros que tenemos una página con una URL que contiene un parámetro llamado id-del-producto, que puede ser cualquiera de los miles de productos que tenemos en nuestra base de datos.

Obviamente, no sería nada práctico crear una ruta para cada producto ya que no sería nada fácil de mantener y a la vez no se aplicarían buenas prácticas.

En su lugar, vamos a definir un parámetro de ruta (una parte de la URL) que se va a usar para extraer el id de nuestro producto con id-del-producto de manera dinámica.

Teniendo como referencia el parámetro para el id del producto, especificamos el parámetro de ruta dentro del directorio con el formato [ <nombre-parametro> ] donde teniendo el id del producto llamado por ejemplo productId, se quedará asignado como [productId] (fijaros en los corchetes, son muy IMPORTANTES).

Dentro del proyecto los especificamos de la siguiente manera:

src/
  └── routes/
      ├── index.tsx                 # /
      ...
      └── product/
          └── [productId]/
                ├── index.tsx       # /product/1234
                └── details/
                    └── index.tsx   # /product/1234/details

En el proyecto se visualiza así:

Con lo que ahora mismo tendríamos dos URLs públicas que siguen este formato:

  1. Página principal del producto: http://127.0.0.1:5173/product/[productId].
  2. Página de detalles del producto: http://127.0.0.1:5173/product/[productId]/details.

En [productId] podemos tener cualquier valor ahora mismo independientemente de la cantidad de productos que tengamos almacenados en nuestra base de datos. Este tema ya no nos debería de preocupar por tener n posibilidades dándonos mucha libertad.

Ahora, lo interesante es pasar a la acción y ver que realmente funcionan bien estas dos nuevas URLs públicas, analizando con más detalles en que opciones no funciona.

Recordad

Reiniciar el servidor en el caso de que no detecte las nuevas rutas parametrizadas.

1. Página del producto principal seleccionado

  • OK: http://127.0.0.1:5173/product/1234.

  • OK: http://127.0.0.1:5173/product/xyz-567.

  • 404: http://127.0.0.1:5173/product/xyz-567/34.

Con una de las opciones, por ejemplo la opción /product/1234, como se puede apreciar se carga el contenido de la página principal del producto, tal como se ha especificado en el componente del fichero index.tsx en la ruta de ficheros src/routes/product/[productId]/index.tsx:

2. Página de detalles del producto seleccionado

  • OK: http://127.0.0.1:5173/product/1234/details

  • 404: http://127.0.0.1:5173/product/1234/detail (IMPORTANTE FIJARSE COMO TERMINA, falta la s)

Con la primera de las opciones, /product/1234/details, se cargará el contenido de la página de detalles del producto, tal como se ha especificado en el componente del fichero index.tsx en la ruta de ficheros src/routes/product/[productId]/details/index.tsx:

Ahora que ya sabemos como añadir información de manera dinámica mediante parámetros de ruta donde hemos añadido el id de un producto, lo que nos haría falta es plasmarlo en la información de nuestra página y esto lo haremos mediante la obtención del parámetro ruta desde una URL.

Obtención del parámetro ruta desde una URL

Una vez que ya disponemos del valor [productId] en la URL, necesitaremos recuperar el valor que se adjunta en ese parámetro y esto lo podemos hacer mediante el uso de la API useLocation().

Documentación oficial Qwik - useLocation()

Podemos ampliar conocimientos con lo que tenemos a continuación: https://shorten-up.vercel.app/iTOhgRj98s

Lo aplicamos en el fichero index.tsx dentro de la carpeta details, cuya ruta es src/routes/product/[productId]/details/index.tsx

Añadimos el uso de useLocation() aplicando de la siguiente forma para coger la información que nos proporciona dicho hook:

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

export default component$(() => {
  // Obtenemos toda la información acerca de la ubicación respecto a la navegación
  const location = useLocation();
  return (
    <div>
      <div>Product Item Details [productId] - Info page</div>
      <p>Href: {location.url.href}</p>                // URL princ. + path + valor de parámetro de ruta
      <p>Pathname: {location.url.pathname}</p>        // path + valor de parámetro de ruta
      <p>Product Id: {location.params.productId}</p>  // Valor de parámetro de ruta
    </div>
  );
});

Una vez guardados los cambios, se reflejará de la siguiente manera introduciendo 1234 como valor del productId en http://127.0.0.1:5173/product/1234/details/.

Para poder obtener el valor del parámetro de ruta en la página principal del producto (src/routes/product/[productId]/index.tsx) debemos de aplicar también el uso de useLocation() y hacer lo mismo.

El código es el siguiente:

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

export default component$(() => {
  // Información acerca de la ubicación respecto a la navegación
  const location = useLocation();
  return (
    <div>
      <div>Product Item [productId] - Principal Page</div>
      <p>Href: {location.url.href}</p>
      <p>Pathname: {location.url.pathname}</p>
      <p>Product Id: {location.params.productId}</p>
    </div>
  );
});

Esto lo estamos aplicando para afianzar lo aprendido y a la vez el resultado que debe de mostrar sería la siguiente información:

Aplicando lo anterior hemos conseguido tener en todo momento localizada la información de navegación sobre el recurso que estamos cargando con la URL pública seleccionada.

Resultado de lo trabajado hasta aquí

Abriendo el proyecto 02-02-routing-advanced podemos encontrar los siguientes cambios:

  • Dos rutas dinámicas con un valor de parámetro llamado productId que forma parte de dos rutas.
  • Las rutas son /product/[productId] (Página principal del producto) y la /product/[productId]/details que será el contenido asociado a los detalles del producto seleccionado

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

Vamos a aprender a continuación como añadir las rutas comodin de un directorio concreto, para facilitar más si sabe nuestro trabajo definiendo este apartado.

Añadir rutas comodín

Además de parametrizar las rutas de un modo sencillo, tenemos la posibilidad de crear nuestras rutas comodín agregando un directorio como [...productIds] (respecto al punto anterior se añaden ... delante de la propiedad del parámetro que en este caso hemos definido como productIds).

Esto va a funcionar como el ejemplo descrito anteriormente, pero también considerará sub-apartados del URI.

Antes de crear el nuevo directorio, eliminamos la carpeta [productId] dentro del directorio src/routes/product.

Ahora que ya hemos eliminado el rastro de la carpeta [productId] añadimos la correspondiente para hacer uso de las rutas comodín llamada [...productIds].

src/
  └── routes/
      └── product/
          └── [...productIds]
                └── index.tsx

Y dentro del fichero index.tsx añadimos el siguiente contenido, donde en el apartado de productIds obtenemos un string concatenado con todos los ids de productos que para mostrarlo como string, lo convertiremos mediante JSON.stringify():

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

export default component$(() => {
  const location = useLocation();
  return (
    <>
      <div>Product Item [...productIds] - Principal Page</div>
      <p>Href: {location.url.href}</p>
      <p>Pathname: {location.url.pathname}</p>
      <p>Product Id: {JSON.stringify(location.params.productIds)}</p>
    </>
  );
});

Usando la configuración establecida, ejecutamos varias url y conseguimos este resultado siguiendo con el uso del hook useLocation():

Podríamos "extraer" esos ids y mostrarlos de una manera más elegante, recorriendo el contenido, mediante la creación de un array con el objetivo de separar estos elementos mediante split usando como parámetro "/":

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

export default component$(() => {
  const location = useLocation();
  return (
    <>
      ....
      <p>Product Id:</p>{' '}
      <ul>
        {location.params.productIds.split('/').map((id, index) => {
            return (id !== '') ? (
                <li>
                  Product select ({index + 1}): {id}
                </li> 
              ): undefined
        })}
      </ul>
    </>
  );
});

Y el resultado, teniendo en cuenta las diferentes URLs públicas que ejecutaremos:

¿Podemos especificar un límite para introducir los datos?

SI, es posible limitar la captura de los parámetros de ruta siempre y cuando añadamos un directorio que lo limite.

Para especificar este límite debemos de añadir un nuevo directorio (ó más de uno) con un nombre descriptivo de la página que limitará esa captura de valores de la ruta.

Por ejemplo, añadimos un directorio llamado details dentro de [...productIds], en el que vamos a obtener todos los ids anteriores y los mostramos de una manera detallada tal y como se hace en la página principal.

src/
  └── routes/
      └── product/
          └── [...productIds]/
                |── index.tsx
                └── details/
                    └──index.tsx

Y este será el código que definiremos en este nuevo fichero, cuyo objetivo será diferenciarlo respecto al index.tsx anterior que sería la página principal (src/routes/product/[...productIds]/index.tsx):

import { component$ } from '@builder.io/qwik';
import { type DocumentHead, useLocation } from '@builder.io/qwik-city';

export default component$(() => {
  const location = useLocation();
  return (
    <>
      <div>Product - Page Details</div>
      <p>Href: {location.url.href}</p>
      <p>Pathname: {location.url.pathname}</p>
      <p>Product Id:</p>{' '}
      <ul>
        {location.params.productIds.split('/').map((id, index) => {
            return (id !== '') ? (
                <li>
                  Producto seleccionado ({index + 1}): {id}
                </li> 
              ): undefined
        })}
      </ul>
    </>
  );
});

La configurado en este nuevo fichero dará opción a capturar esta ruta, limitada por details:

http://127.0.0.1:5173/product/203/204/205/details/

Extrayendo únicamente 203/204/205 como los valores de los ids de los productos. ¿Y cuál es el motivo para no mostrar details?

No se muestra details por considerarlo el final de la ruta y no tiene en cuenta ese valor utilizando el fichero que hemos creado en la siguiente ruta: src/routes/product/[...productIds]/details/index.tsx.

Para considerar ese valor, tendríamos que haber añadido información después del valor .../details/<VALOR NUEVO>.

Sabiendo lo siguiente, el resultado que obtenemos es este:

En cambio, si añadimos 2333 (o cualquier otro dato) después de details cuya URL pública será:

Extraerá 203/204/205/details/2333 como los valores de los ids de los productos ya que details NO se ha especificado como final de la ruta a la hora de la introducción de la URL.

Por ese motivo no usa el fichero index.tsx que se encuentra en la ruta src/routes/product/[...productIds]/details/index.tsx.

El fichero que utilizará se encuentra en la ruta src/routes/product/[...productIds]/index.tsx, mostrándose de la siguiente manera, como se ha especificado anteriormente:

Práctica extra sencilla

¿Qué pensáis que ocurrirá si introducimos la siguiente URL?
http://127.0.0.1:5173/product/203/204/205/details/2333/details

Antes de ejecutar nada, repasar lo aprendido y seguramente ya tengáis la respuesta correcta.

Quizás os suene todo esto un poco confuso, y por ese motivo os animo a que practiquéis añadiendo diferentes variantes en los parámetros y viendo resultados.

Siempre es bueno parar un poco, practicar lo aprendido hasta el momento y asimilar esos conceptos.

Resultado de lo trabajado hasta aquí

Abriendo el proyecto 02-03-routing-wildcard podemos encontrar los siguientes cambios:

  • Uso de rutas comodín donde podemos añadir n valores según demanda.
  • Uso de rutas comodín limitando con un final, especificando la página final y su página personalizada para ese caso de uso

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

Agrupación

En algunos casos, puede surgir la necesidad de mover nuestras carpetas, que definen esas rutas, a un subdirectorio sin que afecte el nombre de la ruta que va a generar Qwik City en función de la estructura de carpetas, para poder tener más orden.

Puede darse el caso que tengamos varias rutas en el mismo nivel que deberían usar diferentes diseños como puede ser parte pública y parte privada por poner un ejemplo conocidos por todo el mundo.

Tanto para la primera situación o la segunda, podemos crear directorios entre paréntesis (p. ej. (products)) que no afectará a las rutas originales.

Lo vamos a reflejar de la siguiente manera:

src/
  └── routes/
        └── (products)/
                ├── detail/
                |     └── index.tsx
                └── preview/
                      └── index.tsx

Añadimos un contenido a cada uno de los ficheros index.tsx para poder visualizarlo:

Realizamos las pruebas correspondientes, especificando una URL pública ignorando completamente el directorio (products):

Como se ha mencionado products NO SERÁ parte del pathname.

¿Qué pasará si pongo products como parte de la URL pública? Probemos con el primero de los casos (SPOILER: al segundo le va a pasar lo mismo).

Resultado de lo trabajado en este apartado

Abriendo el proyecto 02-04-routing-grouped podemos encontrar la siguiente novedad que nos será super útil en el Capítulo 4 - Layouts / Plantilas:

  • Uso de carpetas para ordenar nuestros ficheros y directorios, sin que afecte en la ruta (no se muestre)

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

¿Qué hemos aprendido en este capítulo?

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

Conclusión

Hemos podido aprender las diferentes formas que tenemos para gestionar las rutas y como obtener la información de los parámetros de rutas, desde lo más sencillo a conceptos más avanzados.

Con lo aprendido hasta ahora, podemos pasar al siguiente capítulo donde nos faltaría ver los aspectos de las plantillas anidadas, donde podremos construir diferentes plantillas en base a la ruta, como por ejemplo zona privada y pública y también el enrutamiento en base a los menús. Esto lo veremos a continuación.

Antes de eso, vamos a trabajar con los componentes, un concepto fundamental tanto en Qwik como React y tecnologías similares.