Creación y uso de componentes

Todos los framework de Javascript están basados en componentes. Los componentes son como piezas de un puzzle que nos ayudan a reutilizar código de nuestra interfaz. En vez de tener todo el código en un archivo, creamos componentes que nos ayudan tanto a organizar el código como hacerlo reutilizable, porque después, ese componente, lo podemos utilizar en todas las partes de la aplicación que queramos.

Para agregar componentes al proyecto, dentro del directorio src creamos una carpeta a la cual vamos a llamar components. En esta carpeta vamos a ir creando todos nuestros componentes. Por ejemplo, crearemos un componente que sea la tarjeta de un post para un blog. Llamaremos a este componente PostCard.astro

Los nombres de los archivos de componentes (archivos en src/components), así como los de las plantillas o Layouts que se verán más adelante, se nombran con la primera letra de cada palabra en mayúscula (UpperCamelCase, como las clases de JavaScript). Los componentes de Astro utilizan la extensión .astro al igual que las páginas.

Estructura de un archivo de Astro

Un archivo .astro se estructura en dos partes principales separadas por tres guiones (---). Estas partes son:

  1. Frontmatter (scripts): Se encuentra en la parte superior del archivo, entre dos bloques de tres guiones (---). Aquí se escribe el código JavaScript (o TypeScript) que se ejecuta en el servidor o durante la compilación. Permite definir variables locales, importar otros componentes y gestionar datos antes del renderizado.

  2. Documento HTML: Está situado debajo del segundo bloque de guiones y define la estructura HTML que se mostrará. Si queremos inyectar Javascript en el HTML, se incluye el código entre llaves { }.Los atributos HTML usan el estándar kebab-case (ejemplo: class, data-value).

Crear un archivo de componente

Vamos a crear la siguiente tarjeta usando la etiqueta <article>. A continuación, dejo el código (copiarlo).


  ---

  ---
  <article>
    <img src="/img/gato.webp" alt="gatito">
    <h2>The Post Title</h2>
    <p>A post description</p>
      <p>
      Lorem ipsum dolor sit, amet consectetur adipisicing elit. 
      Neque eius distinctio aspernatur incidunt, 
      tempore expedita architecto est, 
      sint natus, nihil quaerat nisi. 
      Saepe pariatur nihil fugit itaque 
      esse doloribus corporis!
    </p>
    <p>10/12/2023 - 16 min read</p>
  </article>
  

Dentro colocaremos una imagen. Las imágenes se guardan en el directorio public/img/ (inicialmente utilizaremos cualquier imagen de prueba).

Utilizar el componente en una página

Para utilizar un componente en una página (en nuestro caso index.astro), lo importamos en la sección de frontmatter usando el comando import:


  import Post from "../components/Post.astro"

Luego, indicamos dónde lo queremos ver. Para mostrarlo, en la sección de HTML, creamos una etiqueta con el nombre del mismo, en este caso <Post/> (usando la sintaxis de autocierre).

Estilar un componente

Para añadir los estilos al componente Post.astro usaremos tailwind. Una de las ventajas que tiene Astro es que instalar ciertas herramientas es muy fácil. Para ello, desde la terminal ejecutaremos:

pnpm astro add tailwind

Respondemos Y (sí) para instalar automáticamente todas las dependencias necesarias y también para generar el archivo de configuración de tailwind (tailwind.css), es decir dos veces. Con esto, ya podemos comenzar a utilizar tailwind y a dar estilos con él.

Antes de comenzar a cambiar los estilos del componente, se debe importar el archivo global.css en el frontmatter de la página que utilizará el mismo ( en este caso index.astro), como se muestra a continuación:


 ---
 import '../styles/global.css';
 import Post from "../components/Post.astro"
 ---

Para darle estilos al componente Post, utiliza los estilos del ejemplo o los que gustes para el componente.


  ---

  ---
  <article id="article" 
    class="flex max-w-sm flex-col gap-2 rounded bg-gray-800 px-5 py-6 text-white">
    <img class="rounded w-full h-48 object-cover" src="/img/gato.webp" alt="gatito">
    <h2 class="text-2xl font-bold">The Post Title</h2>
    <p>A post description</p>
      <p class="text-zinc-300">
      Lorem ipsum dolor sit, amet consectetur adipisicing elit. 
      Neque eius distinctio aspernatur incidunt, 
      tempore expedita architecto est, 
      sint natus, nihil quaerat nisi. 
      Saepe pariatur nihil fugit itaque 
      esse doloribus corporis!
    </p>
    <p class="text-zinc-300">10/12/2023 - 16 min read</p>
  </article>
	

Usar un componente de forma repetida

Si se quisiera mostrar cuatro post en una página (index.astro), por ejemplo, se puede ir añadiendo la etiqueta con el nombre del componente <Post/> cuatro veces.

Evidentemente todos los cuatro post que se muestran tienen el mismo contenido ya que en estos momentos, el componente es estático. Los componentes estáticos (donde básicamente el contenido no cambia) son excelentes para crear una estructura y diseño básico: encabezados, pies de página, tarjeta del autor, navegación y en general, elementos de la interfaz que no requieren actualizaciones. Ejemplo:


---
import '../styles/global.css';
import Post from "../components/Post.astro"
---
<html lang="es-Es">
	<head>
		<meta charset="utf-8" />
		<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
		<link rel="icon" href="/favicon.ico" />
		<meta name="viewport" content="width=device-width" />
		<meta name="generator" content={Astro.generator} />
		<title>Astro</title>
	</head>
	<body class="container w-5/6 p-4">
		<h1>Usar componente</h1>

		<Post/>
		<Post/>
		<Post/>
		<Post/>

	</body>
</html>

En el navegador, se verían las cuatro tarjetas o posts seguidos:

usar-componente
index.astro

Hacer un componente reutilizable

Para hacer un componente reutilizable se utilizan los props.

Los props son una manera de pasarle datos a un componente para hacerlo reutilizable. Equivaldrían más o menos a los argumentos de una función. Para pasarle información o props a un componente, se pueden usar tres maneras.

  1. Enviarle texto al componente:

    Desde el archivo donde se incluye el componente se pasan los props de manera similar a los argumentos de una función. Ejemplo:

    
      <Post title="Nuevo post" description="Descripción del nuevo post."/>
    

    Y en el componente Post.astro, se leería de la siguiente manera.

    En el frontmatter se escribiría lo siguiente:

    
      ---
      const {title, description } = Astro.props
      ---
    

    Y en el html de Post.astro:

    
      <article id="article" 
        class="flex max-w-sm flex-col gap-2 rounded bg-gray-800 px-5 py-6 text-white">
        <img class="rounded w-full h-48 object-cover" src="/img/gato.webp" alt="gatito">
        <h2 class="text-2xl font-bold">{title}</h2>
        <p>{description}</p>
          <p class="text-zinc-300">
          Lorem ipsum dolor sit, amet consectetur adipisicing elit. 
          Neque eius distinctio aspernatur incidunt, 
          tempore expedita architecto est, 
          sint natus, nihil quaerat nisi. 
          Saepe pariatur nihil fugit itaque 
          esse doloribus corporis!
        </p>
        <p class="text-zinc-300">10/12/2023 - 16 min read</p>
      </article>
    
  2. Enviar un objeto de JavaScript con toda la información

    Las primeras llaves (exteriores) indican que el contenido será código JavaScript, no un texto. Las llaves interiores indican que el código es un objeto. Desde el archivo donde se incluye el componente:

    
      <Post post = {{
        title: ‘Un nuevo post’,
        description: ‘Descripción del nuevo post’
      }}/>
    

    Y en el componente Post.astro, se leería de la siguiente manera:

    En el frontmatter se escribiría lo siguiente:

    
      ---
      const {post} = Astro.props
      ---
    

    Y en el html de Post.astro,

    
      <article id="article" 
        class="flex max-w-sm flex-col gap-2 rounded bg-gray-800 px-5 py-6 text-white">
        <img class="rounded w-full h-48 object-cover" src="/img/gato.webp" alt="gatito">
        <h2 class="text-2xl font-bold">{post.title}</h2>
        <p>{post.description}</p>
          <p class="text-zinc-300">
          Lorem ipsum dolor sit, amet consectetur adipisicing elit. 
          Neque eius distinctio aspernatur incidunt, 
          tempore expedita architecto est, 
          sint natus, nihil quaerat nisi. 
          Saepe pariatur nihil fugit itaque 
          esse doloribus corporis!
        </p>
        <p class="text-zinc-300">10/12/2023 - 16 min read</p>
      </article>
    

    O aplicando destructuración

    
      ---
      const {post: {title, description}} = Astro.props
      ---
    
    
      <article id="article" 
        class="flex max-w-sm flex-col gap-2 rounded bg-gray-800 px-5 py-6 text-white">
        <img class="rounded w-full h-48 object-cover" src="/img/gato.webp" alt="gatito">
        <h2 class="text-2xl font-bold">{title}</h2>
        <p>{description}</p>
        ...
    
  3. Pasar la información desde una Base de Datos

    Uno de los métodos más usuales, es que la información de los post no se escriba de forma estática en el código sino que se tengan los post guardados en una base de datos. Para simular que tenemos una base de datos.

    Para este ejemplo, utilizaremos una librería llamada JSON-Server la cual es muy útil para pruebas porque simula que tenemos una base de datos. Lo haremos todo, en el próximo punto.

Instalar la librería JSON-Server

Para instalar la librería, detenemos el servidor (Ctrl C) y ejecutamos desde la terminal:


  pnpm install -g json-server

La dirección de esta librería es npmjs.com/package/json-server

Una vez instalada, creamos un archivo con un código json y ponemos las distintas propiedades. Esto va a simular que tenemos una base de datos a la cual nos va a dejar hacer peticiones, crear recursos y demás. Lo hacemos en la raíz del proyecto creando un archivo llamado db.json.

En este archivo, pegamos todos los datos que queremos simular que tenemos dentro de una base de datos. Ejemplo:

Para ejecutar la BBDD usamos el siguiente comando desde la terminal:


  json-server --watch db.json

En este caso el nombre de nuestro archivo JSON es db.json.

Se nos dará una URL accesible (Endpoint) https://localhost:3000/posts, desde la cual podemos acceder a los datos. Si la abrimos en el navegador, podemos apreciar que tenemos el JSON con la información de todos los posts.

Traer datos de JSON

Para traer los datos y mostrarlos en los post abrimos el archivo donde se incluye el componente. Usaremos el frontmatter de nuestro archivo ya que la petición se va a ejecutar del lado del servidor.

Desde el frontmatter, podemos hacer peticiones a nuestra base de datos y utilizar datos que no queremos que se expongan en el navegador del cliente.

Hacemos un fetch para traernos datos de la red:


---
import '../styles/global.css';
import Post from "../components/Post.astro";
const respuesta = await fetch("http://localhost:3000/posts");
const post = await respuesta.json();
---

Una de las grandes ventajas de Astro es que tiene una funcionalidad que se conoce como top level await y que básicamente significa que se puede utilizar el await fuera de una función asíncrona. Con lo cual para extraer los post, simplemente utilizamos las dos lineas que están debajo del import.

Como desde el HTML del archivo podemos acceder ahora a cualquier variable que se haya definido en el frontmatter, quitaremos los cuatro componentes <Post/> que habíamos creado de forma estática y mostraremos un post por cada post que tengamos en la base de datos.

Para ello, convertiremos el array de objetos en un array de componentes post utilizando el método .map. Para indicar al HTML en Astro que insertaremos Javascript, utilizamos llaves { }. Accedemos a la variable post y hacemos un map para renderizar por cada uno de los post, un componente <Post/>.


<body class="container w-5/6 p-4 mx-auto">
  <h1class="text-3xl text-center">Leer desde BBDD</h1>
  <!-- div para mostrar los posts en forma de galería -->
  <div class="grid max-w-7xl mx-auto w-full justify-center
  justify-items-center items-center gap-4 px-2 sm:grid-cols-2
  lg:grid-cols-3">
    { post.map((post: any) => <Post post={post}/>) }
  </div>
</body>

El map de Javascript recibe cada uno de los post y por cada post devuelve un componente <Post/> y a ese componente, le pasamos un prop llamado post con el valor de cada uno de los post del array.

En el componente Post.astro, se leería de la siguiente manera:

Frontmatter:


  ---
  const {post: {title, description, content, created_at, image}} = Astro.props
  ---

HTML:

  
  <article class="flex max-w-sm flex-col gap-2 rounded bg-gray-800 
  px-5 py-6 text-white">
    <img class="rounded w-full h-48 object-cover" src={image} alt={title}>
    <h2 class="text-2xl font-bold">{title}</h2>
    <p>{description}</p>
    <p class="text-zinc-300">
      {content.slice(0,100) + "..."}
    </p>
    <p class="text-zinc-300">{new Date(created_at).toLocaleDateString()}</p>
  </article>

Ahora, abrimos una nueva terminal, con el botón +

abrir nueva terminal

Ejecutamos nuevamente el comando para el servidor de desarrollo


  pnpm run dev

Es IMPORTANTE que queden dos procesos corriendo al mismo tiempo, cada uno en su propia pestaña:

Pestaña 1:Servidor de Base de Datos. Ejecutando la "base de datos" con json-server --watch db.json

Pestaña 2: Servidor de Aplicación. Ejecutando el proyecto de Astro con pnpm run dev.

Y si vamos al navegador, podremos ver los distintos posts de la Base de Datos.

Leer desde Base de Datos