Ya que queremos mantener las cosas tan simples como sea posible, no nos preocupemos por los casos de borde en absoluto – proporcionaremos la funcionalidad suficiente para abstraer nuestro anterior ejemplo de Hola Mundo.
Componentes básicos
Escribiremos algunos componentes base: div, p y h1. Para mantener las cosas tan simples como sea posible, forzaremos a cada nodo a contener un id , para que podamos encontrar fácil y rápidamente el elemento DOM real más tarde.
123456789101112131415161718/* * Ayudante para crear la abstracción DOM */constmakeComponent=tag=={a]};(atributos, niños)={if(!atributos |||!atributos. id){thrownewError($0027El componente necesita un id$0027);} return{ tag, attributes, children,};};const div =makeComponent($0027div$0027);const p =makeComponent($0027p$0027);const h1 =makeComponent($0027h1$0027);
javascript
Ahora tenemos las funciones div, p, y h1 en alcance. Si estás en la Programación Funcional, la identificarás como una aplicación parcial. Si no lo estás, puedes ver las funciones como un poco de azúcar sintáctico – no tendrás que proporcionar el argumento de la etiqueta cada vez que necesites un componente.
Componentes más complejos
Ahora que tenemos unos pocos elementos básicos, podemos empezar a componer componentes más complejos. Vamos a introducir el concepto de estado aquí.
Una vez más, porque queremos mantener esto simple, no vamos a entrar en la gestión estatal. Asumamos que el estado está siendo rastreado/administrado en otro lugar.
123456789101112/* * componente de la aplicación - crea un componente ligeramente más complejo a partir de nuestros elementos base */constapp=state===;div({ id:$0027main$0027},[div({ id:$0027header$0027},[h1({ id:$0027title$0027},`Hola, ${state. asunto}!`)]),div({ id:$0027contenido$0027},[p({ id:$0027estático1$0027},$0027Este es un componente estático$0027),p({ id:$0027estático2$0027},$0027Nunca debería tener que ser recreado$0027),]),
javascript
Como pueden ver, acabamos de representar algo similar a la anterior plantilla HTML – pero esta vez en JavaScript. Esta es la esencia básica detrás de JSX. Debajo de la sintaxis del HTML, finalmente se traduce a llamadas de funciones de JavaScript – algo que no es tan fundamentalmente diferente de nuestra ingenua implementación aquí.
En resumen, ese «componente» es una función simple que toma un estado (análogo a nuestro modelo antes mencionado) y devuelve un árbol de DOM Virtual. Asumiendo que nuestro estado se vea así:
123{ subject:$0027World$0027}
javascript
Entonces nuestro árbol DOM debería verse así:
123456789101112131415161718192021222324252627282930313233343536373839404142434445{"tag": "div", "attributes":{"id": "main"}, "children": [{"tag": "div", "attributes":{"id": "header"}, "children":[{"tag": "h1", "attributes":{"id": "title"}, "children": "Hello, World! "}]},{"tag": "div", "attributes":{"id": "content"}, "children":[{"tag": "p", "attributes":{"id": "static1"}, "children": "This is a static component"},{"tag": "p", "attributes":{"id": "static2"}, "children": "It should never have to be re-created"}]}]}
javascript
Renderizando nuestro DOM virtual
No pensaste que nos detendríamos ahí, ¿verdad?
De nuevo, para mantener el tema de esta guía, no construyamos nada demasiado complicado. Escribiremos el código suficiente para cubrir nuestra simple aplicación.
Aquí está el código:
123456789101112131415161718192021222324252627282930/* * Establece los atributos del elemento * elemento: un elemento DOM * atributos: objeto en el formato { atributoNombre: atributoValor } createElement(tag);setAttributes(el, attributes);if((typeof children)===$0027string$0027){/// Si nuestra propiedad "children" es una cadena, sólo establece el innerHTML en nuestro elemento el.innerHTML= children;}else{/// Si no es una cadena, entonces estamos tratando con un arreglo. Renderiza cada niño y luego ejecuta el comando "apppendChild" desde este elemento children.map(renderNode).forEach(el.appendChild.bind(el));}// Finalmente tenemos el nodo y sus niños - devuelve itreturn el;};
javascript
Como puedes ver, esto no es súper sofisticado y no cubre un montón de casos límite, pero es suficiente para nosotros.
Ahora podemos verlo en acción ejecutando el siguiente script (asumiendo que nuestro HTML contiene un elemento con id #root):
123const virtualDOMTree =app({ subject:$0027World$0027});const rootEl =document.querySelector($0027#root$0027);rootEl.appendChild(renderNode(virtualDOMTree));
javascript
Manejo de cambios
Hasta ahora, hemos creado una capa de abstracción DOM, ahora vamos a trabajar en nuestro diferencial.
El primer paso es conseguir dos nodos y comprobar si son diferentes. Usemos el siguiente código:
123456789101112131415161718192021222324252627/* * Ejecuta una comparación superficial entre 2 objetos */constareObjetosDiferentes=(a, b)={// Conjunto de todas las claves únicas (forma rápida y sucia de hacerlo)const allKeys =Array.from(newSet([...Object.keys(a),...Object. keys(b)));// Devuelve verdadero si uno o más elementos son diferentesregresa allKeys.some(k= > a[k]!== b[k]);};/* * Diff 2 nodos * Devuelve verdadero si es diferente, falso si es igual */constareNodesDifferent=(a, b)= >{/// Si al menos uno de los nodos no existe, los consideraremos diferentes. // Además, si el `tag` actual cambió, no necesitamos comprobar nada más.if(!a ||!b |||(a.tag!== b.tag))returntrue;const typeA =typeof a.children;const typeB =typeof b.children;return typeA ! == tipoB // Cubre el caso en el que pasamos de los niños siendo una cadena a un array|||son ObjetosDiferentes(a.atributos, b.atributos)// cambios en los atributos|||(tipoA ===$0027string$0027&& a.children!== b.children);// si es una cadena, ¿cambió el texto?};
javascript
Finalmente, escribamos una función que navegue por nuestro árbol DOM virtual y vuelva a reproducir los elementos si es necesario:
1234567891011121314151617181920/* * Obtiene las representaciones de los nodos anterior y actual * reemplaza la DOM real basándose en si la representación cambió o no */constdiffAndReRender=(previousNode, currentNode)={{if(areNodesDifferent(currentNode, previousNode)){// ¿Es el nodo actual diferente? Si es así, reemplace it.const nodeId = currentNode.attributes.id;console.log($0027Reemplazo del nodo DOM:$0027, nodeId);returndocument.querySelector(`#${nodeId}`).replaceWith(renderNode(currentNode));}elseif(currentNode. childreninstanceofArray){// Si no, y el children prop es un array, vuelve a llamar a esta función para cada child currentNode.children.forEach((currChildNode, index)= >{diffAndReRender(previousNode.children[index], currChildNode);});}}};
javascript
Note que estamos emparejando los niños basados en el índice aquí. Este tipo de emparejamiento no es lo suficientemente bueno para un escenario del mundo real, pero funciona en nuestra aplicación de ejemplo.
Ahora que tenemos una forma de ejecutar un diff y reemplazar quirúrgicamente elementos específicos que realmente cambian, vamos a ejecutar nuestro código de nuevo – esta vez simulando una actualización de estado:
123456789// Renderizar la aplicación inicialconst virtualDOMTree =app({ subject:$0027World$0027});const root =document.querySelector($0027#root$0027);root. appendChild(renderNode(virtualDOMTree));// Generar un nuevo árbol de DOM virtual basado en un cambio de estado:const newVirtualDOMTree =app({ subject:$0027Mom$0027});diffAndReRender(virtualDOMTree, newVirtualDOMTree);
javascript
Después de ejecutar nuestra función de diffAndReRender, veremos un mensaje en la consola que dice Reemplazar el nodo DOM: título. Eso es todo, ningún otro elemento reemplazado. Y de hecho, nuestro elemento #title ahora dirá ¡Hola, mamá!.
Ahora, esto nos da un buen segway en el siguiente segmento.