Llevas tres niveles construyendo el Team Builder. En el Nivel 2 ya hacías que la página cambiara sola con JavaScript: buscabas un elemento, le cambiabas el texto, creabas nodos. Funciona, pero en cuanto la interfaz crece —y un equipo de héroes con filtros, búsqueda y un marcador crece rápido— ese trabajo manual se vuelve frágil. React existe para quitarte ese peso de encima. Este capítulo no entra todavía en todo lo que React sabe hacer: solo responde tres preguntas —qué es, por qué, y cómo arranca— y te pone tu primer componente en la pantalla.
Del Nivel 2 a React: dar pasos vs describir el resultado#
Así actualizabas la página en el Nivel 2. Tú dabas las órdenes, una a una:
// Buscas el elemento en el DOM, igual que aprendiste en el Nivel 2.
const titulo = document.querySelector("h1");
// Y le cambias el contenido tú mismo, a mano.
titulo.textContent = "Team Builder";Esto es código imperativo: una lista de pasos para modificar el DOM. Con un título da igual, pero imagina mantener a mano cada cambio de una lista de héroes que se filtra y se reordena. Tendrías que acordarte de qué tocar, cuándo y en qué orden. Ahí es donde se rompen las cosas.
React le da la vuelta. En vez de dar pasos, describes cómo quieres que se vea la pantalla para unos datos dados, y React se encarga de tocar el DOM por ti: calcula qué ha cambiado y aplica solo eso. A esto se le llama enfoque declarativo. Tú dices qué quieres ver; React resuelve el cómo.
Qué es un componente#
La pieza con la que describes la interfaz en React es el componente. Y un componente, por debajo, no es nada exótico: es una función que devuelve la descripción de lo que se ve.
// Un componente es una función. Su nombre empieza por MAYÚSCULA (App, no app):
// así React lo distingue de una etiqueta HTML normal.
function App() {
// Lo que va en el return describe lo que se verá en la pantalla.
// Esto es JSX: se parece a HTML, pero está escrito dentro de TypeScript.
return <h1>Team Builder</h1>;
}Eso de <h1>Team Builder</h1> escrito en medio del código se llama JSX. Por ahora quédate
con la idea: es la forma de describir interfaz dentro de una función, y se parece al HTML que ya
conoces a propósito. En el próximo capítulo lo verás a fondo (atributos, expresiones, cómo
combinar componentes). Aquí solo necesitas saber que un componente es una función que devuelve
JSX.
La mayúscula no es un capricho de estilo: React la usa para diferenciar tus componentes
(<App />) de las etiquetas del navegador (<h1>, <section>). Si llamas a tu componente
app y escribes <app />, React no busca tu función: cree que es una etiqueta HTML llamada
app, que el navegador no conoce, así que pinta un elemento vacío y tu componente no aparece
—sin error, sin pista—. Por eso: componentes siempre en mayúscula.
Por qué .tsx y no .jsx#
Vienes del Nivel 5, así que ya no escribes JavaScript a secas: escribes TypeScript, con sus
tipos. React encaja con eso sin fricción. El JSX que acabas de ver se escribe dentro de un
fichero .tsx: la t es de TypeScript, y la x es de ese JSX. Es decir, JSX dentro
de TypeScript, los dos en el mismo fichero.
Hay también ficheros .jsx (JSX dentro de JavaScript, sin tipos), pero en este curso todo
React se escribe en .tsx, tipado desde el primer día. No es un extra para más adelante: es
cómo se trabaja en empresa, y te da los mismos avisos del compilador que ya valoraste en el
Nivel 5, ahora también sobre tu interfaz.
Cómo arranca: del fichero a la pantalla#
Un componente, por sí solo, no aparece en ningún sitio: es solo una función. Falta el paso que
lo monta en la página de verdad. Ese paso vive, por convención, en un fichero llamado
main.tsx, y lo da una función de React: createRoot.
Primero, en el HTML de la página hay un contenedor vacío que sirve de punto de anclaje:
<!-- Un único div vacío. React montará tu app aquí dentro. -->
<div id="root"></div>Y en main.tsx enganchas tu componente a ese contenedor:
// react-dom es la parte de React que habla con el DOM del navegador.
// De ahí sacamos createRoot.
import { createRoot } from "react-dom/client";
// Nuestro componente, como antes.
function App() {
return <h1>Team Builder</h1>;
}
// Buscamos el contenedor en el DOM, igual que en el Nivel 2.
// Ojo: getElementById devuelve el elemento... o null si no lo encuentra.
const contenedor = document.getElementById("root");Aquí aparece un detalle de TypeScript que ya conoces del Nivel 5: getElementById devuelve
HTMLElement | null, porque podría no encontrar el nodo. Si intentas montar sobre algo que
quizá es null, el compilador protesta, y con razón. Tienes dos formas de resolverlo.
La rápida es el operador !, que le asegura a TypeScript que ese valor no es null:
// El "!" significa: "este valor no es null, confía en mí".
// Lo usamos porque sabemos que <div id="root"> está en el HTML.
const contenedor = document.getElementById("root")!;
// createRoot crea la raíz de React sobre ese nodo...
// ...y render() pinta nuestro componente dentro.
createRoot(contenedor).render(<App />);El ! es cómodo, pero es una promesa: si te equivocas y el nodo no está, la app revienta en
ejecución. Más adelante, en el ejercicio, verás la alternativa más robusta —comprobar el nodo
en vez de prometer que existe—, que es la que prefiere el código de calidad. Por ahora, con esto
ya tienes una app de React montándose en la pantalla.
Pruébalo#
Este es el arranque completo en un solo fichero: un componente y su montaje. Cámbiale el texto
del <h1> y pulsa Ejecutar para verlo reflejado.
Comprueba lo que sabes#
Pregunta 1 de 4
¿Qué es un componente de React?
Tu turno#
Ejercicio · en esta página
Tu primer componente, en la pantalla
Escribe un componente App que devuelva un <h1> con "Team Builder" y móntalo en el contenedor #root con createRoot. Cuando ejecutes, debe verse renderizado.
Paso 1: Renderiza
- App es una función que empieza por mayúscula y devuelve JSX
- Se monta con createRoot(contenedor).render(<App />) sobre #root
- Al ejecutar, se ve el componente en la pantalla
Paso 2: Muestra datos derivados
- Muestra un héroe real con su nombre y su rol
- El winrate se calcula (victorias / partidas), no se escribe a mano
- Usa {} para insertar el valor en el JSX
- Cada parte va comentada con su porqué
Paso 3: Montaje robusto y limpio
- No fuerza con "!": comprueba que el contenedor existe antes de montar
- Nombres claros y un solo nodo raíz en el JSX
- Se entiende por qué comprobar el nodo es mejor que asumirlo
Ver soluciones
// createRoot monta el árbol de React dentro de un nodo del DOM.
import { createRoot } from "react-dom/client";
// Un componente: una función que empieza por mayúscula y devuelve JSX.
function App() {
// El return describe lo que se ve. Es JSX: parece HTML, pero vive en TypeScript.
return <h1>Team Builder</h1>;
}
// Cogemos el contenedor que la página deja preparado para montar la app.
// getElementById puede devolver null; con "!" le aseguramos a TS que existe.
const contenedor = document.getElementById("root")!;
// createRoot crea la raíz de React en ese nodo; render pinta el componente dentro.
createRoot(contenedor).render(<App />); Por qué este nivel
- Lo mínimo que funciona: un componente que devuelve un <h1> y se monta con createRoot. Renderiza, que es el objetivo del capítulo.
// createRoot monta el árbol de React dentro de un nodo del DOM.
import { createRoot } from "react-dom/client";
// Un componente describe un trozo de interfaz. Su nombre empieza por mayúscula.
function App() {
// Datos del primer héroe del Team Builder (de momento, fijos en el código).
const nombre = "Tracer";
const rol = "Daño";
const partidas = 120;
const victorias = 78;
// El winrate es un dato DERIVADO: no se guarda, se calcula a partir de otros.
const winrate = (victorias / partidas) * 100;
// En JSX, {} inserta un valor de JavaScript dentro de lo que se ve.
return (
<section>
<h1>Team Builder</h1>
<p>
{nombre} ({rol}) — {winrate.toFixed(1)}% de victorias
</p>
</section>
);
}
// getElementById puede ser null; "!" le promete a TS que el nodo existe.
const contenedor = document.getElementById("root")!;
// Creamos la raíz de React y renderizamos el componente dentro.
createRoot(contenedor).render(<App />); Por qué es mejor que el anterior
- Da un paso más: muestra datos reales y CALCULA el winrate en vez de teclear el número. Un dato derivado nunca se guarda a mano: se deriva de su fuente, así no puede quedar desincronizado.
- Introduce {} para meter un valor de JavaScript en el JSX: la primera herramienta para que la interfaz dependa de los datos.
// createRoot monta el árbol de React dentro de un nodo del DOM.
import { createRoot } from "react-dom/client";
// Componente App: describe la interfaz inicial del Team Builder.
function App() {
// Datos fijos del primer héroe (más adelante llegarán desde fuera).
const nombre = "Tracer";
const rol = "Daño";
const partidas = 120;
const victorias = 78;
// Dato derivado: el winrate se calcula, no se almacena.
const winrate = (victorias / partidas) * 100;
// {} inserta valores de JavaScript en la interfaz.
return (
<section>
<h1>Team Builder</h1>
<p>
{nombre} ({rol}) — {winrate.toFixed(1)}% de victorias
</p>
</section>
);
}
// getElementById devuelve HTMLElement | null. En vez de forzar con "!",
// comprobamos que el contenedor existe antes de montar.
const contenedor = document.getElementById("root");
if (contenedor) {
// Solo montamos cuando de verdad hay un nodo donde hacerlo.
createRoot(contenedor).render(<App />);
} Por qué es mejor que el anterior
- El montaje no asume nada: getElementById puede dar null, así que en vez de mentirle al compilador con "!", comprueba el nodo con un if. Si algún día el contenedor no está, la app no revienta.
- Es el reflejo de la regla de calidad del curso: prefieres comprobar a forzar. El "!" calla a TypeScript; el guard resuelve el caso de verdad.