React popularizó la UI basada en componentes, el renderizado declarativo y las vistas impulsadas por estado—llevando a los equipos de código centrado en páginas a sistemas y patrones reutilizables.

React no solo introdujo una nueva librería: cambió lo que los equipos entienden por “arquitectura frontend”. En términos prácticos, la arquitectura frontend es el conjunto de decisiones que mantienen una base de código de UI comprensible a escala: cómo fragmentas la UI en partes, cómo fluyen los datos entre ellas, dónde vive el estado, cómo manejas los efectos secundarios (como las peticiones de datos) y cómo mantienes el resultado testeable y consistente entre el equipo.
Pensar en componentes es tratar cada pieza de la UI como una unidad pequeña y reutilizable que posee su renderizado y puede componerse con otras unidades para construir páginas completas.
Antes de que React se popularizara, muchos proyectos se organizaban alrededor de páginas y manipulación del DOM: “encuentra este elemento, cambia su texto, alterna esta clase”. React empujó a los equipos hacia un valor por defecto distinto:
Estas ideas cambiaron el trabajo diario. En las revisiones de código se empezó a preguntar “¿dónde pertenece este estado?” en lugar de “¿qué selector usaste?”. Diseñadores e ingenieros podían alinearse en un vocabulario de componentes compartido, y los equipos podían construir bibliotecas de bloques de UI sin reescribir páginas enteras.
Aunque un equipo luego cambie a otro framework, muchas costumbres moldeadas por React perduran: arquitectura basada en componentes, renderizado declarativo, flujo de datos predecible y preferencia por componentes reutilizables de sistemas de diseño frente a código único por página. React normalizó estos patrones y eso influyó en el ecosistema frontend en general.
Antes de React, muchos equipos construían interfaces alrededor de páginas, no de unidades de UI reutilizables. Una configuración común eran plantillas renderizadas en el servidor (PHP, Rails, Django, JSP, etc.) que producían HTML, con jQuery por encima para añadir interactividad.
Renderizabas una página y luego la “activabas” con scripts: selectores de fecha, plugins de modales, validadores de formularios, carruseles—cada uno con sus propias expectativas de marcado y ganchos de eventos.
El código solía ser: encuentra un nodo del DOM, adjunta un handler, muta el DOM y espera que nada más se rompa. A medida que la UI crecía, la “fuente de la verdad” se convertía silenciosamente en el propio DOM.
El comportamiento de la UI rara vez vivía en un solo lugar. Estaba repartido entre:
Un solo widget—por ejemplo, un resumen de checkout—podía estar parcialmente construido en el servidor, parcialmente actualizado con AJAX y parcialmente controlado por un plugin.
Este enfoque funcionaba para mejoras pequeñas, pero generaba problemas recurrentes:
Frameworks como Backbone, AngularJS y Ember intentaron aportar estructura con modelos, vistas y routing—a menudo una gran mejora. Pero muchos equipos seguían mezclando patrones, dejando un hueco para una forma más simple de construir UIs como unidades repetibles.
El cambio más importante de React es fácil de decir y sorprendentemente poderoso en la práctica: la UI es una función del estado. En lugar de tratar el DOM como la “fuente de la verdad” y mantenerlo manualmente en sincronía, tratas tus datos como la fuente de la verdad y dejas que la UI sea el resultado.
El estado es simplemente los datos actuales de los que depende tu pantalla: si un menú está abierto, qué hay escrito en un formulario, qué elementos hay en una lista, qué filtro está seleccionado.
Cuando el estado cambia, no vas a cazar por la página para actualizar varios nodos del DOM. Actualizas el estado y la UI vuelve a renderizarse para coincidir con él.
El código tradicional orientado al DOM a menudo termina con lógica de actualización dispersa:
Con el modelo de React, esas “actualizaciones” se convierten en condiciones en la salida del render. La pantalla se vuelve una descripción legible de lo que debe ser visible para un estado dado.
function ShoppingList() {
const [items, setItems] = useState([]);
const [text, setText] = useState("");
const add = () => setItems([...items, text.trim()]).then(() => setText(""));
return (
<section>
<form onSubmit={(e) => { e.preventDefault(); add(); }}>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button disabled={!text.trim()}>Add</button>
</form>
{items.length === 0 ? <p>No items yet.</p> : (
<ul>{items.map((x, i) => <li key={i}>{x}</li>)}</ul>
)}
</section>
);
}
Fíjate cómo el mensaje de vacío, el estado deshabilitado del botón y el contenido de la lista se derivan todos de items y text. Ese es el beneficio arquitectónico: la forma de los datos y la estructura de la UI se alinean, haciendo que las pantallas sean más fáciles de razonar, testear y evolucionar.
React hizo del “componente” la unidad por defecto de trabajo en la UI: una pieza pequeña y reutilizable que agrupa markup, comportamiento y ganchos de estilo detrás de una interfaz clara.
En lugar de dispersar plantillas HTML, listeners y selectores CSS en archivos no relacionados, un componente mantiene las piezas móviles cerca unas de otras. Eso no significa que todo deba vivir en un único archivo, pero sí que el código se organiza en torno a lo que el usuario ve y hace, no en torno a la API del DOM.
Un componente práctico suele incluir:
El cambio importante es que dejas de pensar en “actualiza este div” y empiezas a pensar en “renderiza el Button en su estado deshabilitado”.
Cuando un componente expone un pequeño conjunto de props (entradas) y eventos/callbacks (salidas), es más fácil cambiar su implementación interna sin romper el resto de la app. Los equipos pueden responsabilizarse de componentes o carpetas específicas (por ejemplo, “UI de checkout”) y mejorarlos con confianza.
La encapsulación también reduce el acoplamiento accidental: menos selectores globales, menos efectos secundarios entre archivos, menos sorpresas del tipo “¿por qué dejó de funcionar este click handler?”.
Cuando los componentes se convirtieron en los bloques principales, el código empezó a reflejar el producto:
Esta correspondencia facilita las discusiones de UI: diseñadores, PMs e ingenieros pueden hablar de las mismas “cosas”.
El pensamiento en componentes llevó a muchas bases de código a organizarse por feature o dominio (por ejemplo, /checkout/components/CheckoutForm) y a bibliotecas de UI compartidas (a menudo /ui/Button). Esa estructura escala mejor que carpetas solo por página cuando las funcionalidades crecen y prepara el terreno para sistemas de diseño.
El estilo de render de React suele describirse como declarativo, que es una manera elegante de decir: describes cómo debería ser la UI en una situación dada y React decide cómo hacer que el navegador lo muestre.
En enfoques anteriores orientados al DOM, normalmente escribías instrucciones paso a paso:
Con el renderizado declarativo, en su lugar expones el resultado:
Si el usuario está logueado, muestra su nombre. Si no, muestra un botón de “Iniciar sesión”.
Ese cambio importa porque reduce la cantidad de “contabilidad de la UI” que tienes que hacer. No estás constantemente siguiendo qué elementos existen y qué hay que actualizar: te concentras en los estados posibles de la app.
JSX es básicamente una forma cómoda de escribir la estructura UI cerca de la lógica que la controla. En lugar de separar “archivos de plantilla” y “archivos de lógica” y saltar entre ellos, puedes mantener las piezas relacionadas juntas: la estructura tipo markup, las condiciones, decisiones de formato pequeñas y los handlers.
Esa colocación conjunta es una gran razón por la que el modelo de componentes de React se sintió práctico. Un componente no es solo un trozo de HTML o un paquete de JavaScript: es una unidad de comportamiento de UI.
Una preocupación común es que JSX mezcla HTML y JavaScript, lo que suena como un paso atrás. Pero JSX no es realmente HTML: es sintaxis que produce llamadas en JavaScript. Más importante, React no está mezclando tecnologías tanto como agrupando cosas que cambian juntas.
Cuando la lógica y la estructura UI están fuertemente ligadas (por ejemplo: “muestra un mensaje de error solo cuando falla la validación”), mantenerlas en un único lugar puede ser más claro que dispersar reglas entre archivos separados.
JSX hizo a React accesible, pero el concepto subyacente va más allá de JSX. Puedes escribir React sin JSX y otros frameworks también usan renderizado declarativo con distintas sintaxis de plantillas.
El impacto duradero es la mentalidad: trata la UI como una función del estado y deja que el framework se ocupe de la mecánica de mantener la pantalla en sincronía.
Antes de React, una fuente común de bugs era simple: los datos cambiaban pero la UI no. Los desarrolladores pedían datos nuevos y luego buscaban manualmente los nodos DOM adecuados, actualizaban texto, alternaban clases, añadían/quitaron elementos y mantenían todo eso coherente en casos borde. Con el tiempo, la “lógica de actualización” a menudo se volvía más compleja que la propia UI.
El gran cambio de flujo de trabajo de React es que no indicas al navegador cómo cambiar la página. Describes cómo debería ser la UI para un estado dado y React decide cómo actualizar el DOM real para que coincida.
La reconciliación es el proceso de React para comparar lo que renderizaste la última vez con lo que renderizas ahora y aplicar el conjunto mínimo de cambios al DOM del navegador.
Lo importante no es que React use un “Virtual DOM” como truco mágico de rendimiento. Es que React te da un modelo predecible:
Esa previsibilidad mejora el flujo de trabajo del desarrollador: menos actualizaciones manuales del DOM, menos estados inconsistentes y actualizaciones de UI que siguen las mismas reglas en toda la app.
Al renderizar listas, React necesita una forma estable de emparejar “elementos antiguos” con “elementos nuevos” durante la reconciliación. Para eso sirve key.
{todos.map(todo => (
<TodoItem key={todo.id} todo={todo} />
))}
Usa keys que sean estables y únicas (como un ID). Evita índices de array cuando los items puedan reordenarse, insertarse o eliminarse—si no, React puede reutilizar la instancia de componente equivocada y provocar comportamientos sorprendentes en la UI (por ejemplo, inputs que conservan valores incorrectos).
Uno de los mayores cambios arquitectónicos de React es que los datos fluyen en una sola dirección: de componentes padres a hijos. En lugar de permitir que cualquier parte de la UI “meta la mano” en otras partes y mutar estado compartido, React promueve tratar las actualizaciones como eventos explícitos que suben, mientras que los datos resultantes bajan.
Un padre posee el estado y se lo pasa a un hijo como props. El hijo puede solicitar un cambio llamando a un callback.
function Parent() {
const [count, setCount] = React.useState(0);
return (
<Counter
value={count}
onIncrement={() => setCount(c => c + 1)}
/>
);
}
function Counter({ value, onIncrement }) {
return (
<button onClick={onIncrement}>
Clicks: {value}
</button>
);
}
Fíjate en lo que no sucede: el Counter no modifica count directamente. Recibe value (datos) y onIncrement (la forma de pedir un cambio). Esa separación es el núcleo del modelo mental.
Este patrón hace que los límites sean obvios: “¿Quién posee estos datos?” normalmente se responde con “el ancestro común más cercano”. Cuando algo cambia inesperadamente, lo rastreas hasta el lugar donde vive el estado—no por una red de mutaciones ocultas.
Esa distinción ayuda a los equipos a decidir dónde debe residir la lógica y evita acoplamientos accidentales.
Los componentes que dependen de props son más fáciles de reutilizar porque no dependen de variables globales ni de queries al DOM. También son más simples de testear: puedes renderizarlos con props específicas y afirmar la salida, mientras que el comportamiento con estado se prueba donde se gestiona ese estado.
React empujó a los equipos a alejarse de “jerarquías de clases para UI” y a ensamblar pantallas con piezas pequeñas y enfocadas. En lugar de extender una clase Button base en diez variaciones, normalmente compones comportamiento y visuales combinando componentes.
Un patrón común es construir componentes de layout que no saben nada de los datos que contendrán:
PageShell para header/sidebar/footerStack / Grid para espaciado y alineaciónCard para un marco consistenteEstos componentes aceptan children para que sea la página la que decida qué va dentro, no el layout.
También verás wrappers ligeros como RequireAuth o ErrorBoundary que añaden una preocupación alrededor de lo que envuelven, sin cambiar el interior del componente envuelto.
Cuando necesitas más control que children, los equipos usan un enfoque parecido a “slots” mediante props:
Modal con title, footer y childrenTable con renderRow o emptyStateEsto mantiene los componentes flexibles sin explotar la superficie de la API.
Los árboles de herencia profundos suelen nacer con buenas intenciones (“reutilizaremos la clase base”), pero terminan siendo difíciles de gestionar porque:
Los hooks hicieron la composición aún más práctica. Un custom hook como useDebouncedValue o usePermissions permite que múltiples componentes de feature compartan lógica sin compartir UI. Combínalo con primitivas de UI compartidas (botones, inputs, tipografía) y componentes de feature (CheckoutSummary, InviteUserForm) y obtienes reutilización que sigue siendo comprensible a medida que la app crece.
React hizo natural empezar con estado local en componentes: el valor de un campo, un dropdown abierto, un spinner de carga. Eso funciona bien, hasta que la app crece y varias partes de la UI deben mantenerse en sincronía.
A medida que las funcionalidades se expanden, el estado a menudo debe ser leído o actualizado por componentes que no están en una relación directa padre-hijo. “Simplemente pasa props” se convierte en cadenas largas de props a través de componentes que realmente no se interesan por esos datos. Eso hace que refactorizar sea más arriesgado, aumenta el boilerplate y puede provocar bugs confusos donde dos lugares representan el “mismo” estado por separado.
1) Elevar el estado
Mueve el estado al padre común más cercano y pásalo como props. Suele ser la opción más simple y mantiene las dependencias explícitas, pero puede crear “componentes dios” si se abusa.
2) Context para preocupaciones globales
React Context ayuda cuando muchos componentes necesitan el mismo valor (tema, locale, usuario actual). Reduce el prop drilling, pero si almacenas datos que cambian frecuentemente en el context, puede volverse más difícil razonar sobre las actualizaciones y el rendimiento.
3) Stores externos
A medida que las apps React crecieron, el ecosistema respondió con librerías como Redux y patrones de store similares. Centralizan las actualizaciones de estado, a menudo con convenciones sobre acciones y selectores, lo que puede mejorar la previsibilidad a escala.
Prefiere estado local por defecto, eleva el estado cuando hermanos deban coordinarse, usa context para preocupaciones transversales y considera un store externo cuando muchos componentes distantes dependan de los mismos datos y el equipo necesite reglas claras de actualización. La elección “correcta” depende menos de modas y más de complejidad de la app, tamaño del equipo y frecuencia de cambios en requisitos.
React no solo introdujo una nueva forma de escribir UI: empujó a los equipos hacia un flujo de trabajo impulsado por componentes donde el código, el estilo y el comportamiento se desarrollan como unidades pequeñas y testeables. Ese cambio influyó en cómo se construyen, validan, documentan y despliegan los proyectos frontend.
Cuando la UI está hecha de componentes, es natural trabajar “de fuera hacia adentro”: construye un botón, luego un formulario, luego una página. Los equipos empezaron a tratar los componentes como productos con APIs claras (props), estados previsibles (loading, empty, error) y reglas de estilo reutilizables.
Un cambio práctico: diseñadores y desarrolladores pueden alinearse en un inventario de componentes compartido, revisar comportamiento aisladamente y reducir sorpresas de última hora a nivel de página.
La popularidad de React ayudó a estandarizar una cadena de herramientas moderna que muchos equipos hoy consideran básica:
Aunque no elijas las mismas herramientas, la expectativa permanece: una app React debería tener guardarraíles que atrapen regresiones de UI temprano.
Como extensión reciente de esta mentalidad “workflow-first”, algunos equipos usan plataformas asistidas como Koder.ai para esbozar frontends en React (y el backend alrededor de ellos) desde un flujo de planificación conversacional—útil cuando quieres validar estructura de componentes, propiedad del estado y límites de feature rápidamente antes de invertir semanas en plumbing manual.
Los equipos React también popularizaron la idea de un explorador de componentes: un entorno dedicado donde renderizas componentes en distintos estados, añades notas y compartes una única fuente de verdad sobre su uso.
Este pensamiento “estilo Storybook” (sin requerir un producto específico) cambia la colaboración: puedes revisar el comportamiento de un componente antes de integrarlo en una página y validar casos límite deliberadamente en lugar de confiar en que aparezcan durante QA manual.
Si estás construyendo una librería reutilizable, esto encaja naturalmente con un enfoque de sistema de diseño—ver /blog/design-systems-basics.
La herramienta basada en componentes fomenta PRs más pequeños, revisiones visuales más claras y refactors más seguros. Con el tiempo, los equipos entregan cambios de UI más rápido porque iteran sobre piezas bien acotadas en lugar de navegar código del DOM enmarañado.
Un sistema de diseño, en términos prácticos, son dos cosas que trabajan juntas: una librería de componentes UI reutilizables (botones, formularios, modales, navegación) y las directrices que explican cómo y cuándo usarlos (espaciado, tipografía, tono, reglas de accesibilidad e interacción).
React hizo que este enfoque fuera natural porque “componente” ya es la unidad central de la UI. En lugar de copiar markup entre páginas, los equipos pueden publicar un \u003cButton />, \u003cTextField /> o \u003cDialog /> una vez y reutilizarlo en todo el proyecto—permitiendo aún personalización controlada vía props.
Los componentes React son autocontenidos: pueden agrupar estructura, comportamiento y estilo detrás de una interfaz estable. Eso facilita construir una biblioteca de componentes que sea:
Si partes desde cero, una checklist simple ayuda a evitar que “un montón de componentes” se convierta en un desastre inconsistente: /blog/component-library-checklist.
Un sistema de diseño no es solo consistencia visual: es consistencia de comportamiento. Cuando un modal siempre ata el foco correctamente o un dropdown siempre soporta navegación por teclado, la accesibilidad pasa a ser la opción por defecto en lugar de una ocurrencia tardía.
El theming también se simplifica: puedes centralizar tokens (colores, espaciados, tipografía) y dejar que los componentes los consuman, de modo que cambios de marca no requieran tocar cada pantalla.
Para equipos que evalúan si vale la pena invertir en componentes compartidos, la decisión suele ligarse a la escala y al coste de mantenimiento; algunas organizaciones conectan esa evaluación con planes de plataforma como /pricing.
React no solo cambió cómo construimos UIs: cambió cómo evaluamos la calidad. Cuando tu app está compuesta por componentes con entradas claras (props) y salidas (UI renderizada), las pruebas y el rendimiento se convierten en decisiones arquitectónicas, no en arreglos de última hora.
Los límites de componente permiten probar en dos niveles útiles:
Esto funciona mejor cuando los componentes tienen propiedad clara: un lugar que posee el estado y hijos que principalmente muestran datos y emiten eventos.
Las apps React suelen sentirse rápidas porque los equipos planean el rendimiento en la estructura:
Una regla útil: optimiza las partes “caras”—listas grandes, cálculos complejos y áreas que se re-renderizan frecuentemente—en lugar de perseguir pequeñas ganancias.
Con el tiempo, los equipos pueden deslizarse hacia trampas comunes: sobre-componentizar (demasiadas piezas minúsculas con propósito poco claro), prop drilling (pasar datos a través de muchas capas) y límites difusos donde nadie sabe qué componente “posee” cierto estado.
Cuando vas rápido (especialmente con código auto-generado o scaffolded), las mismas trampas aparecen más rápido: los componentes se multiplican y la propiedad se vuelve borrosa. Tanto si codificas a mano como si usas una herramienta como Koder.ai para generar una app React con backend (por ejemplo, Go y PostgreSQL), la regla es la misma: deja explícita la propiedad del estado, mantén las APIs de los componentes pequeñas y refactoriza hacia límites claros por feature.
Server Components, meta-frameworks y mejores herramientas seguirán evolucionando la forma en que se entregan las apps React. La lección perdurable no cambia: diseña alrededor del estado, la propiedad y bloques de UI componibles, y deja que las pruebas y el rendimiento sigan naturalmente.
Para decisiones de estructura más profundas, consulta /blog/state-management-react.
React replanteó la arquitectura frontend en torno a unas pocas decisiones centrales:
El efecto práctico es menos gestión manual del DOM y límites más claros para equipos y herramientas.
Pensar en componentes significa tratar cada pieza de la UI como una unidad pequeña y reutilizable que controla su renderizado y puede componerse en pantallas más grandes. En la práctica, un componente agrupa:
Esto desplaza el trabajo de “actualiza este nodo del DOM” a “renderiza este componente para este estado”.
En el código centrado en el DOM, el DOM suele convertirse en la fuente de la verdad, por lo que actualizas manualmente varios elementos para mantener la UI coherente. En React, actualizas el estado y renderizas en función de él, por lo que condiciones como spinners de carga, botones deshabilitados y estados vacíos se mantienen coherentes de forma natural.
Una buena prueba: si escribes muchos pasos de “buscar elemento y alternar clase”, estás peleando contra el modelo; si la UI se desajusta, normalmente es un problema de propiedad del estado.
Antes de React, muchas apps eran centradas en páginas: plantillas renderizadas en el servidor más jQuery y plugins. El comportamiento estaba repartido entre vistas del servidor, atributos HTML e inicializadores JS.
Los problemas comunes incluían:
React empujó a los equipos hacia componentes reutilizables y actualizaciones previsibles.
El renderizado declarativo significa describir qué debe verse en la UI para un determinado estado, no cómo mutar el DOM paso a paso.
En lugar de:
Expresas condiciones en la salida del render (por ejemplo: “si está logueado muestra su nombre, si no muestra un botón de 'Entrar'”), y React se encarga de actualizar el DOM real.
JSX facilitó colocar la estructura de la UI junto con la lógica que la controla (condiciones, formato, handlers). Eso reduce el ida y vuelta entre archivos de plantilla y archivos de lógica.
JSX no es HTML; se compila a JavaScript. El beneficio clave es organizativo: agrupar lo que cambia junto (UI + comportamiento) dentro de un componente suele ser más sencillo de mantener.
La reconciliación es cómo React compara la salida del render anterior con la nueva y aplica el menor conjunto de actualizaciones al DOM.
La conclusión práctica es previsibilidad: escribes la lógica de render como si reconstruyeras la UI desde cero, y React aplica los cambios incrementalmente.
Para listas, usa valores key estables y únicos (por ejemplo, IDs). Evita índices de array cuando los elementos puedan reordenarse o insertarse, porque React podría reutilizar una instancia de componente equivocada (por ejemplo, inputs que mantienen el valor incorrecto).
El flujo de datos unidireccional significa que los datos pasan de padre a hijo vía props, mientras que los hijos solicitan cambios mediante callbacks.
Esto aclara límites:
Depurar suele convertirse en “encuentra dónde vive el estado” en lugar de rastrear mutaciones ocultas en código no relacionado.
La composición consiste en ensamblar comportamiento combinando componentes en lugar de usar jerarquías de clases.
Patrones comunes:
Una progresión práctica para el estado es:
Elige según la complejidad de la app y las necesidades del equipo, no por tendencias.
children (shells, grids, cards)RequireAuth o ErrorBoundaryfooter, emptyState, renderRow) cuando children no bastaSe mantiene la flexibilidad sin árboles de herencia profundos ni efectos en cascada por cambios en una clase base.