JavaScript básico, bibliotecas y la búsqueda de una representación DOM con estado

 

 

 

  • Implemente rápidamente. Implementar inteligentemente
  • Typography Masterclass, with Elliot Jay Stocks

  • Índice
    1. Categorías de marcado
    2. Navegador de colores
    3. Representación del lado del cliente
    4. Interactividad
    5. Renderizado
    6. Representación incremental
    7. Actus imperatus
    8. Conclusión

    Está bien establecido que la web enfrenta una amplia gama de problemas de usabilidad y rendimiento, desde patrones de interfaz de usuario hostiles al usuario y resultados de búsqueda retorcidos hasta un rendimiento lento y un exceso de batería. Si bien las fuerzas subyacentes pueden estar más allá del control de los desarrolladores, las opciones tecnológicas cuestionables generalmente desempeñan un papel importante, a menudo en el ámbito del JavaScript del lado del cliente. Con el fin de mejorar nuestra comprensión colectiva de este atolladero autoinfligido, examinemos un aspecto pequeño pero significativo donde los desarrolladores toman las riendas: pintar píxeles en la pantalla.

     

    En su artículo fundamental “ El mercado de los limones ”, el renombrado experto en Internet Alex Russell expone los innumerables fallos de nuestra industria, centrándose en las desastrosas consecuencias para los usuarios finales. Esta indignación es enteramente procedente según los estatutos de nuestro medio .

    Los marcos son un factor importante en esa ecuación, pero también puede haber buenas razones para que los desarrolladores front-end elijan un marco o biblioteca : actualizar dinámicamente las interfaces web puede ser complicado en formas no obvias. Investiguemos empezando desde el principio y volviendo a los primeros principios.

    Categorías de marcado

    Todo en la web comienza con el marcado, es decir, HTML. Las estructuras de marcado se pueden dividir aproximadamente en tres categorías:

    1. Piezas estáticas que siempre permanecen iguales.
    2. Partes variables que se definen una vez al crear una instancia.
    3. Partes variables que se actualizan dinámicamente en tiempo de ejecución.

    Por ejemplo, el encabezado de un artículo podría verse así:

    header h1«Hello World»/h1 small«123» backlinks/small/header

    Las partes variables aquí están envueltas en «guillemets»: “Hello World” es el título respectivo, que solo cambia entre artículos. Sin embargo, el contador de vínculos de retroceso puede actualizarse continuamente mediante secuencias de comandos del lado del cliente; Estamos listos para volvernos virales en la blogósfera. Todo lo demás sigue siendo idéntico en todos nuestros artículos.

     

    El artículo que estás leyendo ahora se centra en la tercera categoría: Contenido que debe actualizarse en tiempo de ejecución.

    Navegador de colores

    Imagine que estamos creando un navegador de colores simple: un pequeño widget para explorar un conjunto predefinido de colores con nombre , presentado como una lista que combina una muestra de color con el valor de color correspondiente. Los usuarios deberían poder buscar nombres de colores y alternar entre códigos de color hexadecimales y tripletes de rojo, azul y verde (RGB). Podemos crear un esqueleto inerte con sólo un poco de HTML y CSS:

    Consulte el lápiz [Color Browser (inerte) [bifurcado]] (https://codepen.io/smashingmag/pen/RwdmbGd) de FND .

    Representación del lado del cliente

    Hemos decidido a regañadientes emplear renderizado del lado del cliente para la versión interactiva. Para nuestros propósitos aquí, no importa si este widget constituye una aplicación completa o simplemente una isla autónoma incrustada dentro de un documento HTML estático o generado por el servidor.

    Dada nuestra predilección por JavaScript básico (consulte los primeros principios y todo), comenzamos con las API DOM integradas del navegador:

    function renderPalette(colors) { let items = []; for(let color of colors) { let item = document.createElement("li"); items.push(item); let value = color.hex; makeElement("input", { parent: item, type: "color", value }); makeElement("span", { parent: item, text: color.name }); makeElement("code", { parent: item, text: value }); } let list = document.createElement("ul"); list.append(...items); return list;}

    Nota:
    Lo anterior se basa en una pequeña función de utilidad para una creación de elementos más concisa:

    function makeElement(tag, { parent, children, text, ...attribs }) { let el = document.createElement(tag); if(text) { el.textContent = text; } for(let [name, value] of Object.entries(attribs)) { el.setAttribute(name, value); } if(children) { el.append(...children); } parent?.appendChild(el); return el;}

    También habrás notado una inconsistencia estilística: dentro del itemsbucle, los elementos recién creados se adjuntan a su contenedor. Más adelante, invertimos las responsabilidades, ya que el listcontenedor ingiere elementos secundarios.

    Voilà: renderPalettegenera nuestra lista de colores. Agreguemos un formulario para interactividad:

    function renderControls() { return makeElement("form", { method: "dialog", children: [ createField("search", "Search"), createField("checkbox", "RGB") ] });}

    La createFieldfunción de utilidad encapsula las estructuras DOM necesarias para los campos de entrada; es un pequeño componente de marcado reutilizable:

     

    function createField(type, caption) { let children = [ makeElement("span", { text: caption }), makeElement("input", { type }) ]; return makeElement("label", { children: type === "checkbox" ? children.reverse() : children });}

    Ahora solo nos falta combinar esas piezas. Vamos a envolverlos en un elemento personalizado:

    import { COLORS } from "./colors.js"; // an array of `{ name, hex, rgb }` objectscustomElements.define("color-browser", class ColorBrowser extends HTMLElement { colors = [...COLORS]; // local copy connectedCallback() { this.append( renderControls(), renderPalette(this.colors) ); }});

    De ahora en adelante, un color-browserelemento en cualquier parte de nuestro HTML generará allí mismo toda la interfaz de usuario. (Me gusta pensar en ello como una macro que se expande en el lugar). Esta implementación es algo declarativa 1 , con estructuras DOM que se crean componiendo una variedad de generadores de marcado sencillos, componentes claramente delineados, por así decirlo.

    1 La explicación más útil que he encontrado sobre las diferencias entre programación declarativa e imperativa se centra en los lectores. Desafortunadamente, esa fuente en particular se me escapa, así que estoy parafraseando aquí: el código declarativo representa el qué , mientras que el código imperativo describe el cómo . Una consecuencia es que el código imperativo requiere un esfuerzo cognitivo para recorrer secuencialmente las instrucciones del código y construir un modelo mental del resultado respectivo.

    Interactividad

    En este punto, simplemente estamos recreando nuestro esqueleto inerte; todavía no hay interactividad real. Controladores de eventos al rescate:

    class ColorBrowser extends HTMLElement { colors = [...COLORS]; query = null; rgb = false; connectedCallback() { this.append(renderControls(), renderPalette(this.colors)); this.addEventListener("input", this); this.addEventListener("change", this); } handleEvent(ev) { let el = ev.target; switch(ev.type) { case "change": if(el.type === "checkbox") { this.rgb = el.checked; } break; case "input": if(el.type === "search") { this.query = el.value.toLowerCase(); } break; } }}

    Nota:
    handleEvent significa que no tenemos que preocuparnos por el enlace de funciones . También viene con varias ventajas . Otros patrones están disponibles.

    Cada vez que cambia un campo, actualizamos la variable de instancia correspondiente (a veces llamada enlace de datos unidireccional). Lamentablemente, el cambio de este estado interno 2 no se refleja en ninguna parte de la interfaz de usuario hasta el momento.

    2 En la consola de desarrollador de su navegador, verifique document.querySelector("color-browser").querydespués de ingresar un término de búsqueda.

    Tenga en cuenta que este controlador de eventos está estrechamente vinculado a renderControlslos componentes internos porque espera una casilla de verificación y un campo de búsqueda, respectivamente. Por lo tanto, cualquier cambio correspondiente renderControls(tal vez cambiar a botones de opción para representaciones de color) ahora debe tener en cuenta este otro fragmento de código: ¡ acción a distancia ! Ampliar el contrato de este componente para incluir nombres de campos podría aliviar esas preocupaciones. Fulares Portabebes

     

    Ahora nos enfrentamos a una elección entre:

    1. Acceder a nuestro DOM creado previamente para modificarlo, o
    2. Recreándolo incorporando un nuevo estado.

    Renderizado

    Como ya hemos definido nuestra composición de marcado en un solo lugar, comencemos con la segunda opción. Simplemente volveremos a ejecutar nuestros generadores de marcas y les proporcionaremos el estado actual.

    class ColorBrowser extends HTMLElement { // [previous details omitted] connectedCallback() { this.#render(); this.addEventListener("input", this); this.addEventListener("change", this); } handleEvent(ev) { // [previous details omitted] this.#render(); } #render() { this.replaceChildren(); this.append(renderControls(), renderPalette(this.colors)); }}

    Hemos movido toda la lógica de renderizado a un método dedicado 3 , que invocamos no solo una vez al inicio sino cada vez que cambia el estado.

    3 Es posible que desee evitar las propiedades privadas , especialmente si es posible que otras se basen en su implementación.

    A continuación, podemos convertirnos colorsen un captador para devolver solo entradas que coincidan con el estado correspondiente, es decir, la consulta de búsqueda del usuario:

    class ColorBrowser extends HTMLElement { query = null; rgb = false; // [previous details omitted] get colors() { let { query } = this; if(!query) { return [...COLORS]; } return COLORS.filter(color = color.name.toLowerCase().includes(query)); }}

    Nota:
    soy partidario del patrón de rebote .
    La alternancia de representaciones de colores se deja como ejercicio para el lector. Puede ingresar this.rgby renderPaletteluego completar codecon color.hexo color.rgb, tal vez empleando esta utilidad:

    function formatRGB(value) { return value.split(","). map(num = num.toString().padStart(3, " ")). join(", ");}

    Esto ahora produce un comportamiento interesante (realmente molesto):

    Consulte el lápiz [Color Browser (defectuoso) [bifurcado]](https://codepen.io/smashingmag/pen/YzgbKab) de FND .

    Introducir una consulta parece imposible ya que el campo de entrada pierde el foco después de que se realiza un cambio, dejando el campo de entrada vacío. Sin embargo, ingresar un carácter poco común (por ejemplo, “v”) deja claro que algo está sucediendo: la lista de colores efectivamente cambia.

    La razón es que nuestro enfoque actual de "hágalo usted mismo" (DIY) es bastante tosco: #renderborra y recrea el DOM al por mayor con cada cambio. Al descartar los nodos DOM existentes también se restablece el estado correspondiente, incluido el valor, el foco y la posición de desplazamiento de los campos del formulario. ¡Eso no es bueno!

     

    Representación incremental

    La interfaz de usuario basada en datos de la sección anterior parecía una buena idea: las estructuras de marcado se definen una vez y se vuelven a representar a voluntad, en función de un modelo de datos que representa claramente el estado actual. Sin embargo, el estado explícito de nuestro componente es claramente insuficiente; Necesitamos conciliarlo con el estado implícito del navegador mientras lo volvemos a renderizar.

    Claro, podríamos intentar hacer explícito ese estado implícito e incorporarlo a nuestro modelo de datos, como incluir un campo o propiedades. Pero eso todavía deja muchas cosas sin tener en cuenta, incluida la gestión del enfoque, la posición de desplazamiento y una miríada de detalles en los que probablemente ni siquiera hemos pensado (con frecuencia, eso significa funciones de accesibilidad). ¡En poco tiempo, estaremos recreando efectivamente el navegador!valuechecked

    En su lugar, podríamos intentar identificar qué partes de la interfaz de usuario necesitan actualizarse y dejar el resto del DOM intacto. Desafortunadamente, eso está lejos de ser trivial, que es donde bibliotecas como React entraron en juego hace más de una década: en la superficie, proporcionaron una forma más declarativa de definir estructuras DOM 4 (al mismo tiempo que fomentaban la composición en componentes, estableciendo una única fuente de verdad). para cada patrón de interfaz de usuario individual). En el fondo, dichas bibliotecas introdujeron mecanismos 5 para proporcionar actualizaciones DOM granulares e incrementales en lugar de recrear árboles DOM desde cero, tanto para evitar conflictos de estado como para mejorar el rendimiento 6 .

    4 En este contexto, eso significa esencialmente escribir algo que parezca HTML, lo cual, dependiendo de tu sistema de creencias , es esencial o repugnante. El estado de las plantillas HTML era algo grave en aquel entonces y sigue siendo deficiente en algunos entornos.
    5 El artículo “ Aprendamos cómo funcionan los marcos JavaScript modernos creando uno ” de Nolan Lawson proporciona muchas ideas valiosas sobre ese tema. Para obtener aún más detalles, vale la pena estudiar la documentación para desarrolladores de lit-html .
    6 Desde entonces hemos aprendido que algunos de esos mecanismos son en realidad tremendamente costosos .

    La conclusión: si queremos encapsular definiciones de marcado y luego derivar nuestra interfaz de usuario a partir de un modelo de datos variables, tenemos que depender de una biblioteca de terceros para la conciliación.

    Actus imperatus

    En el otro extremo del espectro, podríamos optar por modificaciones quirúrgicas. Si sabemos a qué apuntar, el código de nuestra aplicación puede llegar al DOM y modificar solo aquellas partes que necesitan actualización.

    Lamentablemente, sin embargo, ese enfoque generalmente conduce a un acoplamiento calamitosamente estrecho, con lógica interrelacionada esparcida por toda la aplicación mientras que las rutinas específicas violan inevitablemente la encapsulación de los componentes. Las cosas se vuelven aún más complicadas cuando consideramos permutaciones de UI cada vez más complejas (piense en casos extremos, informes de errores, etc.). Esos son precisamente los problemas que las bibliotecas antes mencionadas esperaban erradicar.

     

    En el caso de nuestro navegador de color, eso significaría encontrar y ocultar entradas de color que no coincidan con la consulta, sin mencionar reemplazar la lista con un mensaje sustituto si no quedan entradas coincidentes. También tendríamos que intercambiar representaciones de color en el lugar. Probablemente puedas imaginar cómo el código resultante terminaría disolviendo cualquier separación de preocupaciones, alterando elementos que originalmente pertenecían exclusivamente a renderPalette.

    class ColorBrowser extends HTMLElement { // [previous details omitted] handleEvent(ev) { // [previous details omitted] for(let item of this.#list.children) { item.hidden = !item.textContent.toLowerCase().includes(this.query); } if(this.#list.children.filter(el = !el.hidden).length === 0) { // inject substitute message } } #render() { // [previous details omitted] this.#list = renderPalette(this.colors); }}

    Como dijo una vez un hombre sabio : ¡Eso es demasiado conocimiento!

    Las cosas se vuelven aún más peligrosas con los campos de formulario: no sólo podríamos tener que actualizar el estado específico de un campo, sino que también necesitaríamos saber dónde inyectar mensajes de error. Si bien llegar hasta ya renderPaletteera bastante malo, aquí tendríamos que perforar varias capas: createFieldes una utilidad genérica utilizada por renderControls, que a su vez es invocada por nuestro nivel superior ColorBrowser.

    Si las cosas se ponen complicadas incluso en este ejemplo mínimo, imagine tener una aplicación más compleja con aún más capas e direcciones indirectas. Mantenerse al tanto de todas esas interconexiones se vuelve casi imposible. Estos sistemas suelen convertirse en una gran bola de barro en la que nadie se atreve a cambiar nada por miedo a romper cosas sin darse cuenta.

    Conclusión

    Parece haber una omisión flagrante en las API de navegador estandarizadas. Nuestra preferencia por soluciones JavaScript estándar libres de dependencias se ve frustrada por la necesidad de actualizar de forma no destructiva las estructuras DOM existentes. Eso supone que valoramos un enfoque declarativo con encapsulación inviolable, también conocido como "Ingeniería de software moderna: las partes buenas".

    Tal como está actualmente, mi opinión personal es que una biblioteca pequeña como lit-html o Preact a menudo está justificada, particularmente cuando se emplea teniendo en cuenta la reemplazabilidad : ¡aún podría existir una API estandarizada! De cualquier manera, las bibliotecas adecuadas ocupan poco espacio y normalmente no representan un gran obstáculo para los usuarios finales, especialmente cuando se combinan con mejoras progresivas .

    Sin embargo, no quiero dejarte colgado, así que he engañado a nuestra implementación básica de JavaScript para que haga principalmente lo que esperamos:

    Consulte el Pen [Color Browser [bifurcado]](https://codepen.io/smashingmag/pen/vYPwBro) de FND .

    (yk)Explora más en

    • javascript
    • Marcos
    • Técnicas
    • Codificación





    Tal vez te puede interesar:

    1. 40 bibliotecas útiles de JavaScript
    2. Bibliotecas prácticas de JavaScript y complementos de jQuery
    3. Bibliotecas de JavaScript útiles y complementos de jQuery: parte 2
    4. Bibliotecas JavaScript útiles y complementos jQuery

    JavaScript básico, bibliotecas y la búsqueda de una representación DOM con estado

    JavaScript básico, bibliotecas y la búsqueda de una representación DOM con estado

    Implemente rápidamente. Implementar inteligentemente Typography Masterclass, with Elliot Jay Stocks Índice

    programar

    es

    https://aprendeprogramando.es/static/images/programar-javascript-basico-1183-0.jpg

    2024-04-04

     

    JavaScript básico, bibliotecas y la búsqueda de una representación DOM con estado
    JavaScript básico, bibliotecas y la búsqueda de una representación DOM con estado

    Si crees que alguno de los contenidos (texto, imagenes o multimedia) en esta página infringe tus derechos relativos a propiedad intelectual, marcas registradas o cualquier otro de tus derechos, por favor ponte en contacto con nosotros en el mail [email protected] y retiraremos este contenido inmediatamente

     

     

    Top 20