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.
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.
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.
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.
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.
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.
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:
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:
Inyectamos data.href
en href
, que es un atributo de <a></a>
. También añadimos _blank
como valor del atributo target
para inyectarlo directamente en formato string.
Inyectamos data.text
en el contenido dentro de <a></a>
y también en el título principal que será un h1
.
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:
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.
Si cumple
deberá de mostrar un mensaje así: Como "tienes más de 40 años", tienes 18 años y más de 22 años de experiencia ;)
Si NO se cumple
la condición: Eres joven, todavía tienes 37 años y te faltan 3 para volver a cumplir 18 años ;)
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:
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:
Título
en el que inyectamos el valor name
.Texto condicional
que SI cumple la condición ya que age
es mayor que 40 siendo su valor 56.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
:
0_hobby
: leer.1_hobby
: deporte.2_hobby
: videojuegos.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.
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:
Independiente de la jerarquía: los componentes se pueden cargar desordenados. Por ejemplo, el código de un componente secundario puede cargarse antes que su código principal.
Según la interacción: la carga del código se difiere hasta que un usuario interactúa con un componente.
Más que solo componentes: Qwik carga de forma diferida cualquier cierre, incluidos componentes, detectores de eventos, efectos y comportamientos.
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 oficialSi 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:
Cualquier variable de alcance léxico
debe declararse como const
.
una variable/símbolo ($
) obtenido debe ser: a.- serializable / b.- importable (ya sea desde un archivo diferente como import o desde este archivo usando export).
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:
<Title />
<Age />
<Hobbies />
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
.
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.
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:
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.
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
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:
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>
);
};
export const Age = ({ age }: AgeProps) => {
return (
<div style='border:2.5px solid yellow; padding: 5px'>
<p>
...
</p>
</div>
);
};
export const Hobbies = ({ hobbiesList }: HobbiesProps) => {
return (
<div style='border:4px solid green; padding: 5px'>
<ul>
...
</ul>
</div>
);
};
export default component$(() => {
return (
<div style='border: 3px dotted purple; padding: 10px'>
...
</div>
);
});
Cuyo resultado será el siguiente:
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
Al finalizar este capítulo, deberíamos de ser capaces de:
props
.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!!