10 puntos por GN⁺ 2025-04-23 | 3 comentarios | Compartir por WhatsApp
  • 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 templateclone() → 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

 
wfedev 2025-04-24

Al final, termina siendo con componentes web..

 
ahwjdekf 2025-04-23

Felicidades. Ha nacido otro framework de js.

 
GN⁺ 2025-04-23
Opiniones de Hacker News
  • Para muchos desarrolladores de JS esto podría ser una herejía, pero creo que la variable state es un antipatrón

    • Uso web components y, en lugar de agregar variables de estado para tipos de variables "planas", uso value/textContent/checked de los elementos del DOM, etc., como única fuente de verdad
    • Agrego setters y getters cuando hace falta
    • Aunque haya menos código, de forma natural más cosas funcionan correctamente
    • Al usar WebComponents, se separa el objeto de la plantilla HTML adyacente, lo que genera una modularidad más tipo fusilli o macarrones que código espagueti
  • La documentación dice que este enfoque es muy mantenible, pero no estoy de acuerdo

    • El patrón de diseño se basa únicamente en convenciones
    • Cuando varios desarrolladores trabajan al mismo tiempo en una app compleja, es muy probable que al menos uno se salga de esas convenciones
    • Los frameworks de UI basados en clases, como UIKit de iOS, obligan a todos los desarrolladores a usar un conjunto estándar de APIs, lo que hace que el código sea predecible y fácil de mantener
  • Últimamente estoy escribiendo aplicaciones en TypeScript "vanilla" junto con vite, y cada vez cuestiono más las "mejores" prácticas de frontend

    • No puedo sacar conclusiones sobre la escalabilidad, pero en rendimiento hay grandes ventajas
    • Es divertido, se aprende mucho, depurar es simple y la arquitectura es fácil de entender
    • Lo que más extraño es el templating
  • Este enfoque me recuerda a la vieja librería backbone js

    • Incluso hay un repositorio de GitHub con ejemplos del patrón MVC adaptado a la plataforma web
  • Hace poco pensé en algo parecido, pero sin usar elementos template

    • Uso funciones y template literals para devolver strings, y los pongo en el innerHTML de un elemento existente o creando un nuevo elemento div
    • Las funciones quedan anidadas y es difícil estructurarlas de una forma razonable
  • Este 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

    • Tengo más afinidad con el backend, así que pienso que las interacciones relacionadas con seguridad deberían pasar por el servidor
    • Veo JS como una forma de agregar funcionalidad del lado del cliente sobre una base sólida de HTML y CSS
  • Uso un helper similar a React.createElement

    • Hay un ejemplo funcional de un dashboard de servidor simulado
  • Estoy trabajando en deja-vu.junglecoder.com como un intento de construir un toolkit JS para herramientas basadas en HTML

    • Todavía no he resuelto bien el data binding reactivo/bidireccional, pero grab/patch está bastante bien
    • La forma en que usa plantillas hace que mover partes de una plantilla sea muy fácil
  • En mi primer trabajo formal después de graduarme de la universidad, trabajé creando una versión web de un software en Delphi

    • El equipo ya iba por la tercera reescritura del frontend y había que cambiar de framework
    • Yo insistía en que debíamos escribir nuestro propio framework, pero al equipo no le gustó mi propuesta
    • Después me fui porque recibí una mejor oferta en otra empresa
    • Más adelante probé otro framework llamado tiny.js, y lo sigo usando en proyectos personales