Saltar al contenido
Under the Hood
Available in Spanish only

React

¿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í:

  1. 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.
  2. Se ejecuta el componente y produce una nueva descripción de la UI.
  3. El framework compara esa descripción con la anterior: el virtual DOM en React y el render tree en Blazor.
  4. 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

CartItem.tsx
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

CartItem.razor
<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 memo en React o ShouldRender en 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 hace initialQuantity en 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 StateHasChanged para 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.

    React
  • React

    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