¿Qué es un componente? Lo aplicamos en React y Blazor
Última revisión:
General- Conceptos básicos
- Blazor
Respuesta corta
Un componente es una pieza encapsulada y reutilizable de la interfaz: recibe datos desde afuera, puede tener estado propio y describe cómo debe verse la UI para esos datos.
Renderizarlo es ejecutarlo para obtener esa descripción; luego el framework la compara con la anterior y actualiza en el DOM solo lo que cambió.
En esta página
Explicación
Una pieza de la interfaz
Imagina que estás jugando con Legos. Tienes piezas distintas, cada una con su forma y su función, y combinándolas puedes construir una nave, un avión o un robot.
Las interfaces de usuario se construyen de la misma forma. Cada pieza es un componente, y combinando componentes se arma desde una pantalla hasta una aplicación completa.
Un componente, entonces, es una pieza que agrupa la estructura, la lógica y el estilo de una parte de la UI: un botón, una tarjeta de producto, una línea del carrito. Está encapsulado: desde afuera solo se interactúa con él a través de su interfaz, es decir, los datos que recibe y los eventos que emite. Por eso se puede reutilizar en distintos lugares.
Lo que lo hace distinto de una pieza de Lego es que es declarativo: describo cómo debe verse la UI para ciertos datos y el framework se encarga de actualizar la pantalla cuando esos datos cambian. No manipulo el DOM a mano.
Función o clase
En React, un componente es una función que recibe props y devuelve JSX. En Blazor, es una clase: el archivo .razor se compila a una clase que hereda de ComponentBase, con el markup arriba y la lógica en un bloque @code. La idea es la misma y las piezas se corresponden:
| Concepto | React | Blazor |
|---|---|---|
| Qué es | Una función que devuelve JSX | Una clase .razor |
| Datos desde el padre | Props | Propiedades con [Parameter] |
| Estado propio | useState |
Campos privados |
| Avisar al padre | Una función recibida en las props | EventCallback |
| Inicializar | El valor inicial de useState |
OnInitialized |
| Reaccionar a datos nuevos | Código en el cuerpo de la función | OnParametersSet |
En pocas palabras, las props son lo que llega desde afuera y el estado es lo que el componente maneja por su cuenta. La diferencia entre ambos y los hooks tienen sus propias entradas.
Qué es un render
Renderizar no es pintar la pantalla. Es ejecutar el componente para obtener la descripción de cómo debería verse la UI. El ciclo completo es así:
- Algo lo dispara. Cambia su estado, o su padre se renderiza y le pasa props nuevas. En Blazor, además, después de cada evento de la UI (como un clic) se vuelve a renderizar automáticamente.
- Se ejecuta el componente y produce una nueva descripción de la UI.
- El framework compara esa descripción con la anterior: el virtual DOM en React y el render tree en Blazor.
- Aplica en el DOM solo las diferencias. Si nada cambió, el DOM no se toca.
¿La mayor diferencia entre los dos? React vuelve a ejecutar la función completa en cada render, así que sus variables locales nacen de nuevo cada vez y el estado tiene que guardarlo React por fuera; de ahí vienen los hooks. En Blazor, la instancia del componente persiste entre renders y solo se vuelve a armar el markup, por eso el estado puede vivir en un campo privado.
Componentes en árbol
Los componentes se anidan y forman un árbol: un Cart contiene varios CartItem. Los datos bajan del padre al hijo como props o parámetros, y los eventos suben del hijo al padre como callbacks. El hijo no modifica los datos del padre: le avisa que algo pasó y el padre decide qué hacer.
Código
El mismo CartItem en los dos frameworks: recibe el producto desde el padre, maneja su propia cantidad y avisa cuando hay que quitarlo. Las líneas resaltadas son las equivalentes.
React
import { useState } from 'react';
type CartItemProps = { name: string; unitPrice: number; initialQuantity: number; onRemove: () => void;};
export function CartItem({ name, unitPrice, initialQuantity, onRemove }: CartItemProps) { const [quantity, setQuantity] = useState(initialQuantity); // estado: lo guarda React const unitPriceText = unitPrice.toFixed(2); // se recalcula en cada render const subtotal = quantity * unitPrice;
return ( <li> <span> {name} (${unitPriceText} c/u) </span> <button onClick={() => setQuantity(quantity - 1)} disabled={quantity === 1}> − </button> <span>{quantity}</span> <button onClick={() => setQuantity(quantity + 1)}>+</button> <span>${subtotal.toFixed(2)}</span> <button onClick={onRemove}>Quitar</button> </li> );}Blazor
<li> <span>@Name ($@unitPriceText c/u)</span> <button @onclick="() => quantity--" disabled="@(quantity == 1)">−</button> <span>@quantity</span> <button @onclick="() => quantity++">+</button> <span>$@(Subtotal.ToString("N2"))</span> <button @onclick="OnRemove">Quitar</button></li>
@code { [Parameter] public string Name { get; set; } = ""; [Parameter] public decimal UnitPrice { get; set; } [Parameter] public int InitialQuantity { get; set; } [Parameter] public EventCallback OnRemove { get; set; }
private int quantity; // estado: un campo privado private string unitPriceText = ""; private decimal Subtotal => quantity * UnitPrice;
// se ejecuta una vez, al crear el componente protected override void OnInitialized() => quantity = InitialQuantity;
// se ejecuta cada vez que el padre le pasa parámetros protected override void OnParametersSet() => unitPriceText = UnitPrice.ToString("N2");}Trade-offs
- Granularidad. Un componente gigante que hace de todo no se reutiliza ni se prueba fácil. Uno demasiado pequeño obliga a pasar props por muchos niveles. El criterio que uso es una responsabilidad clara por componente.
- El re-render tiene costo, aunque normalmente es bajo gracias a la comparación. En árboles grandes o componentes pesados existen optimizaciones, como
memoen React oShouldRenderen Blazor, pero se aplican cuando hay un problema medido, no por defecto. - El error típico. Creer que renderizar es actualizar el DOM, o modificar una prop o un
[Parameter]desde dentro del componente. En React las props son de solo lectura; en Blazor, ese valor se pisa en el siguiente render del padre. Si el hijo necesita cambiarlo, lo copia a su estado, como haceinitialQuantityen el ejemplo. - Función frente a clase. El modelo de React es simple y predecible, pero como la función se vuelve a ejecutar entera, el estado vive fuera de ella y hacen falta los hooks. El de Blazor es intuitivo para quien viene de C#, pero si el estado cambia fuera de un evento de la UI (un timer o un servicio), hay que llamar a
StateHasChangedpara que se vuelva a renderizar.
Entradas relacionadas
React
Hooks de React: cómo un componente recuerda entre renders
Los hooks son funciones que se enganchan a React para que un componente de función recuerde valores entre renders, lea contexto o se sincronice con algo externo, cosas que antes solo podían hacer las clases. Se llaman siempre en el mismo orden y en el nivel superior del componente, porque React los identifica por ese orden.
ReactReact
useEffect() vs useMemo(): ¿cuándo y cómo se elige cada uno?
useEffect() sincroniza el componente con algo externo después del render y no produce un valor para mostrar. useMemo() calcula un valor durante el render y lo guarda en caché. Si un dato se puede derivar de props o estado, se calcula en el render, nunca con un efecto.
React