learning-front

Nivel 6 · React de cero a héroe (con TypeScript)

JSX y componentes

JSX a fondo (expresiones con llaves, atributos, un solo nodo raíz y Fragment), componer componentes y qué hace React por dentro con el Virtual DOM (y por qué no es el Shadow DOM).

En el capítulo anterior viste que un componente es una función que devuelve JSX, y que ese JSX se parece a HTML. Aquí abrimos esa caja: qué es exactamente el JSX, cómo meter datos dentro, cómo combinar componentes entre sí, y qué hace React por debajo para que todo esto sea rápido.

JSX: HTML que en realidad es JavaScript#

JSX es la sintaxis con la que describes la interfaz dentro de un componente. Se parece al HTML del Nivel 1 a propósito, pero no es HTML: es JavaScript disfrazado. Antes de llegar al navegador, una herramienta lo transforma en llamadas normales de JS que crean los elementos.

tsx
// Esto que escribes...
const saludo = <h1>Team Builder</h1>;

// ...se transforma, por debajo, en algo así (una llamada que crea el elemento):
// const saludo = jsx("h1", { children: "Team Builder" });

No tienes que escribir esa segunda forma nunca: la herramienta de build lo hace por ti. Pero saber que el JSX acaba siendo JavaScript explica casi todas sus reglas, como verás enseguida.

Meter valores con llaves {}#

Dentro del JSX, unas llaves {} abren una ventana a JavaScript: lo que pongas dentro se evalúa y su resultado aparece en la interfaz. Esto es lo que hace que la pantalla dependa de tus datos.

tsx
// Una variable dentro del JSX:
const nombre = "Tracer";
// {nombre} evalúa la variable y coloca su valor en el texto.
const titulo = <h2>{nombre}</h2>;

No solo variables: dentro de {} cabe cualquier expresión (cualquier trozo de JS que devuelva un valor).

tsx
// Una operación: se calcula y se muestra el resultado.
const victorias = 78;
const partidas = 120;
// {} con una cuenta dentro: React muestra 65.
const winrate = <span>{(victorias / partidas) * 100}</span>;
tsx
// Un ternario (Nivel 2): elige qué texto mostrar según una condición.
const activo = true;
// Si activo es true muestra "En forma"; si no, "En reserva".
const estado = <span>{activo ? "En forma" : "En reserva"}</span>;

La palabra clave es expresión: algo que devuelve un valor. Por eso dentro de {} no caben sentencias como if o for (esas no devuelven nada). Para decidir, usas el ternario; para listas, lo verás en el capítulo de listas.

Atributos en JSX#

Los atributos se parecen a los de HTML, pero con dos diferencias que vienen de que el JSX es JavaScript.

La primera: class es una palabra reservada de JavaScript (sirve para crear clases, Nivel 2), así que en JSX el atributo se llama className. Si por costumbre escribes class, React te lo avisa en la consola del navegador (te dice que seguramente querías className): no rompe la app, pero es la señal de que el atributo correcto es className y de que tus estilos quizá no se están aplicando como crees.

tsx
// En HTML sería class="tarjeta"; en JSX es className.
const caja = <div className="tarjeta">Contenido</div>;

Por la misma razón, el atributo for de las etiquetas <label> se escribe htmlFor:

tsx
// En HTML sería <label for="email">; en JSX es htmlFor.
const etiqueta = <label htmlFor="email">Email</label>;

Los estilos en línea no son un texto, sino un objeto de JavaScript, y por eso van con dobles llaves: las de fuera son la ventana a JS, las de dentro son el objeto. Las propiedades van en camelCase (backgroundColor, no background-color).

tsx
// {{ }} = una ventana a JS ({...}) que contiene un objeto ({...}) de estilos.
const aviso = <p style={{ color: "tomato", fontWeight: 600 }}>Atención</p>;

En la práctica preferirás className y una hoja de estilos (como en el ejercicio) antes que estilos en línea, pero conviene saber leerlos.

Por último, en JSX toda etiqueta tiene que cerrarse. Las que en HTML podían ir solas (<img>, <br>, <input>) se cierran con />:

tsx
// Etiqueta sin contenido: se autocierra con />.
const logo = <img src="/tracer.png" alt="Tracer" />;

Un solo nodo raíz (y el Fragment)#

Un componente, como una función, devuelve una sola cosa. En JSX eso significa un único nodo raíz: no puedes devolver dos elementos sueltos uno al lado del otro.

tsx
// ESTO NO COMPILA: dos elementos sueltos, sin un padre que los envuelva.
function Mal() {
  return (
    <h2>Tracer</h2>
    <p>Daño</p>
  );
}

Una opción es envolverlos en un <div>, pero eso mete en el DOM un nodo que quizá no quieres. Para esos casos React trae el Fragment: un envoltorio invisible, <>...</>, que agrupa sin añadir nada al DOM.

tsx
// El Fragment <>...</> agrupa los dos elementos sin crear un <div> de más.
function Bien() {
  return (
    <>
      <h2>Tracer</h2>
      <p>Daño</p>
    </>
  );
}

Comentarios dentro del JSX#

Dentro del JSX, un // normal no vale (rompería el marcado). Los comentarios van en una expresión {}, con la forma {/* ... */}:

tsx
// Comentario JSX: va entre llaves, con la forma {/* ... */}.
const tarjeta = (
  <article>
    {/* El nombre del héroe */}
    <h2>Tracer</h2>
  </article>
);

Componer componentes#

Aquí está la idea grande de React. Un componente puede usar a otros, como si fueran etiquetas nuevas. Construyes pantallas combinando piezas pequeñas, cada una con su responsabilidad. A eso se le llama composición.

tsx
// Una pieza pequeña: la cabecera.
function Header() {
  return <h1>Team Builder</h1>;
}

// Otra pieza: la tarjeta de un héroe.
function HeroCard() {
  return <article>Tracer — Daño</article>;
}

// App COMPONE la interfaz usando las otras como etiquetas: <Header /> y <HeroCard />.
function App() {
  return (
    <>
      <Header />
      <HeroCard />
    </>
  );
}

Fíjate en la mayúscula: <Header /> (tuyo) frente a <article> (del navegador). React usa esa mayúscula inicial para distinguir tus componentes de las etiquetas HTML. Por ahora cada pieza lleva sus datos fijos dentro; en el próximo capítulo aprenderás a pasarles datos con props, y ahí los componentes se vuelven de verdad reutilizables.

Qué hace React por dentro: el Virtual DOM#

En el Nivel 2 tocabas el DOM a mano, y tocar el DOM es caro: cada cambio puede obligar al navegador a recalcular y repintar. React evita ese coste con el Virtual DOM: una copia ligera del DOM que mantiene en memoria.

Cuando algo cambia, React no repinta toda la página. Genera una versión nueva del Virtual DOM, la compara con la anterior para ver qué ha cambiado de verdad —ese proceso se llama reconciliación— y aplica al DOM real solo las diferencias. Tú describes el qué (con JSX); React calcula el cómo mínimo.

Cuidado con un parecido que confunde: el Shadow DOM no es esto. El Shadow DOM es una función del navegador para los web components, que encapsula marcado y estilos para que no se mezclen con el resto de la página. Suena parecido, pero no tiene nada que ver con el Virtual DOM: uno es una técnica interna de React para actualizar rápido; el otro, un mecanismo de aislamiento del navegador.

Pruébalo#

La tarjeta del Team Builder, ya en componentes: App compone Header y HeroCard, con expresiones {} para el winrate. Cambia un dato o un texto y pulsa Ejecutar.

Comprueba lo que sabes#

Pregunta 1 de 4

¿Qué es JSX?

Tu turno#

Ejercicio · en esta página

La tarjeta de héroe, en componentes

Crea un componente HeroCard con el nombre y el rol de un héroe (datos fijos), usando className, y compón la interfaz desde App. El styles.css ya está hecho.

Paso 1: Compone y renderiza

  • HeroCard es una función que empieza por mayúscula y devuelve JSX
  • Muestra el nombre y el rol del héroe, con className en el contenedor
  • App usa <HeroCard /> (composición) y se ve la tarjeta con estilo
Ver soluciones
// HeroCard: un componente con la tarjeta de un héroe.
// Sus datos van fijos en el código; pasárselos desde fuera es el próximo capítulo (props).
function HeroCard() {
  // Un componente devuelve UN SOLO nodo raíz: aquí, el <article> de la tarjeta.
  return (
    <article className="tarjeta">
      {/* El nombre del héroe */}
      <h2 className="tarjeta__nombre">Tracer</h2>
      {/* Su rol dentro del equipo */}
      <p className="tarjeta__rol">Daño</p>
    </article>
  );
}

// App se exporta por defecto para que el motor lo monte.
export default function App() {
  // App usa HeroCard como si fuera una etiqueta más: eso es composición.
  return <HeroCard />;
}

Por qué este nivel

  • Lo esencial: un componente HeroCard que devuelve un solo nodo raíz y un App que lo compone. La interfaz ya no es una sola pieza: son componentes que se combinan.