- Writing JavaScript Views the Hard Way: un artículo que explica cómo construir vistas con JavaScript puro sin frameworks
- Mediante un enfoque imperativo directo, se logran rendimiento, mantenibilidad y portabilidad
- Se separan claramente las actualizaciones de estado y las actualizaciones del DOM, siguiendo convenciones estrictas de nombres y patrones estructurales según cada rol
- Este método facilita la depuración, garantiza compatibilidad con todos los navegadores y tiene la gran ventaja de 0 dependencies
- Puede ser difícil para principiantes, pero al aprenderlo ofrece una comprensión profunda de cómo funcionan realmente los sistemas
Escribir vistas de JavaScript a la 'Hard Way'
¿Qué es esto?
- Este enfoque es un patrón para construir vistas solo con JavaScript sin frameworks como React, Vue o lit-html
- No es una librería ni una herramienta específica, sino el patrón de codificación en sí, que evita el problema del código espagueti
- Al usar un método imperativo directo, reduce la abstracción y aumenta la intuición
Ventajas frente a los frameworks
- Rendimiento: al ser código imperativo, funciona sin cálculos innecesarios y es adecuado tanto para hot paths como para cold paths
- 0 dependencies: evita problemas de upgrades de librerías o compatibilidad
- Portabilidad: el código escrito puede portarse a cualquier framework
- Mantenibilidad: la estructura clara por secciones y las convenciones de nombres facilitan ubicar el código
- Soporte de navegadores: es compatible con la mayoría de los navegadores desde IE9, e incluso puede soportar IE6 con algunos ajustes
- Facilidad de depuración: ofrece stack traces poco profundos sin capas intermedias
- Estructura funcional: aunque no usa inmutabilidad, todos los componentes están basados en funciones
Explicación de la estructura
Estructura general
- Está compuesta por
template → clone() → función init()
- La función
init() crea una instancia de vista que incluye variables de estado, referencias al DOM, funciones de actualización, event listeners, etc.
Ejemplo de estructura de código (Hello World)
const template = document.createElement('template');
template.innerHTML = `<div>Hello <span id="name">world</span>!</div>`;
function clone() {
return document.importNode(template.content, true);
}
function init() {
let frag = clone();
let nameNode = frag.querySelector('#name');
let name;
function setNameNode(value) {
nameNode.textContent = value;
}
function setName(value) {
if(name !== value) {
name = value;
setNameNode(value);
}
}
function update(data = {}) {
if(data.name) setName(data.name);
return frag;
}
return update;
}
Estructura interna de la función init()
1. Variables del DOM
frag es el fragmento de plantilla generado desde clone()
- Los elementos internos se referencian con
querySelector() y los nombres de variables usan la forma fooNode
2. Vistas del DOM
- Partes que incluyen otras vistas (subvistas reutilizables)
- Ejemplo:
let updateChildView = childView();
- Las funciones de actualización de vistas se nombran con la forma
updateFoo
3. Variables de estado
- Valores de datos que pueden cambiar dentro de la vista
- Para actualizar el DOM eficientemente, se compara con el valor actual y solo se modifica el DOM cuando es necesario
4. Funciones de actualización del DOM
- Se usan al cambiar el estado de los elementos del DOM
- Ejemplo:
function setNameNode(value) {
nameNode.textContent = value;
}
- La manipulación del DOM debe realizarse únicamente dentro de estas funciones
5. Funciones de actualización de estado
- Incluyen la lógica de cambio de estado y su reflejo en el DOM
- Los valores que no cambian se ignoran para evitar cambios innecesarios en el DOM
- Ejemplo:
function setName(value) {
if(name !== value) {
name = value;
setNameNode(value);
}
}
Funciones template y clone()
template
- Crea una estructura HTML estática con el elemento
<template>
- No se inserta directamente en el DOM; se crea una copia mediante clone
clone()
- Duplica con
document.importNode(template.content, true)
- Si es necesario, puede devolver el elemento raíz usando
.firstElementChild
Forma de interacción
Flujo de datos padre → hijo
- El padre llama a
init() del hijo para obtener la función de actualización, y la invoca con la forma update({ name: 'foo' })
Propagación de datos basada en eventos
- Por defecto sigue el modelo props down, events up
- Las vistas hijas se comunican despachando eventos hacia arriba
Comparación con React
constructor() (React) → init() (Hard Way)
- Se encarga de la configuración inicial del componente
render() (React) → update(data) (Hard Way)
- Cumple la función de refrescar la pantalla y actualizar la UI
this.setState() (React) → setX(value) (Hard Way)
- Se reemplaza por una forma de establecer directamente los valores de estado
props (React) → valores pasados mediante update(data) (Hard Way)
- Forma de procesar los datos enviados desde el componente padre
- JSX / Virtual DOM (React) → plantillas HTML + API del DOM (Hard Way)
- En lugar de una UI declarativa, usa manipulación manual del DOM y plantillas
Conclusión
- Este enfoque, frente a frameworks más conocidos, tiene una barrera de entrada inicial más alta, pero ofrece fortalezas como:
- Optimización de rendimiento
- Control total
- Comprensión profunda mediante el aprendizaje
- Separando funciones por rol y siguiendo convenciones de nombres, es posible construir una UI mantenible sin frameworks
Compatibilidad
- Aunque los ejemplos recientes usan APIs para navegadores modernos, también puede darse soporte hasta IE9 o inferiores mediante alternativas basadas en funciones
- También puede extenderse hasta IE6 usando un enfoque de pasar funciones por props en lugar de eventos
3 comentarios
Al final, termina siendo con componentes web..
Felicidades. Ha nacido otro framework de js.
Opiniones de Hacker News
Para muchos desarrolladores de JS esto podría ser una herejía, pero creo que la variable
statees un antipatrónvalue/textContent/checkedde los elementos del DOM, etc., como única fuente de verdadLa documentación dice que este enfoque es muy mantenible, pero no estoy de acuerdo
Últimamente estoy escribiendo aplicaciones en TypeScript "vanilla" junto con vite, y cada vez cuestiono más las "mejores" prácticas de frontend
Este enfoque me recuerda a la vieja librería backbone js
Hace poco pensé en algo parecido, pero sin usar elementos
templateinnerHTMLde un elemento existente o creando un nuevo elementodivEste código se ve exactamente igual al código de actualización manual que las librerías de vistas reactivas intentan reemplazar
Llevo programando unos 20 años, pero nunca me acostumbré a los frameworks de frontend
Uso un helper similar a
React.createElementEstoy trabajando en deja-vu.junglecoder.com como un intento de construir un toolkit JS para herramientas basadas en HTML
grab/patchestá bastante bienEn mi primer trabajo formal después de graduarme de la universidad, trabajé creando una versión web de un software en Delphi