React
Componentes funcionales y pureza
Explica cómo los componentes funcionales transforman props y estado en UI, por qué el render debe ser puro y dónde ubicar efectos y mutaciones.
- Última actualización
- Actualizada
- Nivel
- Fundamentos
React
Explica cómo los componentes funcionales transforman props y estado en UI, por qué el render debe ser puro y dónde ubicar efectos y mutaciones.
Un componente de React es una unidad de interfaz que recibe entradas y calcula elementos. Su valor no depende de cuántas líneas tenga, sino de la responsabilidad que representa.
Los componentes modernos se escriben normalmente como funciones:
type ProductCardProps = {
name: string;
price: number;
};
function ProductCard({ name, price }: ProductCardProps) {
return (
<article>
<h2>{name}</h2>
<p>{price.toLocaleString("es-CO", { style: "currency", currency: "COP" })}</p>
</article>
);
}La función recibe props y devuelve un árbol de elementos de React. React puede ejecutarla cada vez que necesita calcular el siguiente resultado de la interfaz.
React distingue los nombres por la primera letra:
function Header() {
return <header>Mi tienda</header>;
}
function App() {
return <Header />;
}<Header /> representa un componente propio.<header> representa un elemento del host environment, en este caso el DOM.Los componentes comienzan con mayúscula. Una etiqueta en minúscula se interpreta como un elemento nativo del entorno.
Conceptualmente:
props + state + context
↓
componente
↓
elementos de ReactLa salida no es HTML almacenado ni un nodo del DOM. Es una descripción que React utiliza durante reconciliation.
Un componente también puede devolver:
null para no mostrar contenido.React espera que el render sea puro e idempotente:
let nextId = 1;
function Product() {
const id = nextId++; // Incorrecto durante render
return <article id={`product-${id}`}>Producto</article>;
}El resultado cambia por ejecutar la función, no por cambiar sus entradas. Strict Mode puede revelar este problema al repetir renders en desarrollo.
Una alternativa es recibir el identificador como prop o generarlo en el lugar responsable de crear el dato.
Render puede realizar cálculos puros:
function CartSummary({ prices }: { prices: number[] }) {
const total = prices.reduce((sum, price) => sum + price, 0);
return <p>Total: {total}</p>;
}También puede crear arrays, objetos o funciones locales. Hacerlo no es automáticamente un problema. Solo se vuelve relevante cuando existe una razón semántica o de rendimiento para conservar una referencia.
El trabajo externo pertenece a otro momento:
function ExportButton() {
function handleClick() {
downloadReport();
}
return (
<button type="button" onClick={handleClick}>
Descargar reporte
</button>
);
}downloadReport no se ejecuta durante render, sino cuando el usuario activa el botón.
Evita declarar un componente dentro de otro:
function Profile() {
function Avatar() {
return <img src="/avatar.webp" alt="" />;
}
return <Avatar />;
}Cada render crea una nueva función Avatar. Para React, el tipo puede ser diferente y su estado puede reiniciarse.
function Avatar() {
return <img src="/avatar.webp" alt="" />;
}
function Profile() {
return <Avatar />;
}Decláralo dentro únicamente cuando no sea un componente, sino una función auxiliar que devuelve datos o cuando comprendas deliberadamente la identidad resultante.
Un componente merece existir cuando representa una frontera útil:
La cantidad de líneas es una señal, no una regla.
Un componente puede estar haciendo demasiado cuando:
function CheckoutPage() {
// user loading, cart calculations, address form,
// payment flow and complete page layout in one component
}La solución no es separar cada div, sino identificar responsabilidades como AddressForm, OrderSummary y PaymentSection.
La fragmentación también tiene coste:
function ProductName({ children }: { children: React.ReactNode }) {
return <span>{children}</span>;
}Si no añade semántica, comportamiento, estilo reutilizable o una API útil, puede aumentar indirección sin aportar valor.
Las props son el contrato visible:
type AlertProps = {
tone: "info" | "success" | "warning" | "danger";
title: string;
children: React.ReactNode;
};Una buena API:
Diferencia los datos del dominio de los estados visuales:
type ButtonProps = {
variant?: "primary" | "secondary";
isLoading?: boolean;
disabled?: boolean;
children: React.ReactNode;
};
function Button({
variant = "primary",
isLoading = false,
disabled = false,
children,
}: ButtonProps) {
return (
<button
type="button"
className={`button button--${variant}`}
disabled={disabled || isLoading}
aria-busy={isLoading}
>
{isLoading ? "Procesando…" : children}
</button>
);
}El comportamiento del componente debe seguir siendo comprensible incluso cuando los estilos viven en CSS.
Un componente controlado recibe el valor actual y comunica cambios:
<Tabs value={activeTab} onValueChange={setActiveTab} />Uno no controlado puede administrar su estado interno y aceptar un valor inicial:
<Tabs defaultValue="overview" />Ambos modelos pueden ser válidos. El diseño depende de quién debe poseer y coordinar el estado.
Los class components siguen existiendo en código legado y actualmente son necesarios para implementar Error Boundaries directamente con la API base. No son la forma principal para enseñar componentes nuevos.
class LegacyCounter extends React.Component {
state = { count: 0 };
render() {
return <p>{this.state.count}</p>;
}
}El notebook los trata como interoperabilidad y legado, no como base de la ruta.
Header() en lugar de renderizar <Header />.useCallback.JSX: sintaxis, expresiones y reglas explica cómo escribimos el árbol de elementos que devuelve un componente.