Emularea React și JSX în Vanilla JS
Publicat: 2022-03-11Puțini oameni nu le plac cadrele, dar chiar dacă sunteți unul dintre ele, ar trebui să luați notă și să adoptați caracteristicile care ușurează puțin viața.
În trecut, m-am opus utilizării cadrelor. Cu toate acestea, în ultimul timp, am avut experiența de a lucra cu React și Angular în unele dintre proiectele mele. Primele două ori mi-am deschis editorul de cod și am început să scriu cod în Angular, mi s-a părut ciudat și nefiresc; mai ales după mai bine de zece ani de codare fără a folosi niciun framework. După un timp, am decis să mă angajez să învăț aceste tehnologii. Foarte repede a devenit evidentă o mare diferență: a fost atât de ușor să manipulați DOM, atât de ușor să ajustați ordinea nodurilor atunci când era necesar și nu a fost nevoie de pagini și pagini de cod pentru a construi o interfață de utilizare.
Deși încă prefer libertatea de a nu fi atașat la un cadru sau o arhitectură, nu aș putea ignora faptul că crearea elementelor DOM într-una este mult mai convenabilă. Așa că am început să caut modalități de a emula experiența în vanilla JS. Scopul meu este să extrag unele dintre aceste idei din React și să demonstrez cum aceleași principii pot fi implementate în JavaScript simplu (deseori denumit vanilla JS) pentru a ușura puțin viața dezvoltatorului. Pentru a realiza acest lucru, haideți să construim o aplicație simplă pentru răsfoirea proiectelor GitHub.
Indiferent de modul în care construim un front-end folosind JavaScript, vom accesa și vom manipula DOM. Pentru aplicația noastră, va trebui să construim o reprezentare a fiecărui depozit (miniatură, nume și descriere) și să o adăugăm în DOM ca element de listă. Vom folosi API-ul de căutare GitHub pentru a ne prelua rezultatele. Și, din moment ce vorbim despre JavaScript, să căutăm în depozitele JavaScript. Când interogăm API-ul, obținem următorul răspuns JSON:
{ "total_count": 398819, "incomplete_results": false, "items": [ { "id": 28457823, "name": "freeCodeCamp", "full_name": "freeCodeCamp/freeCodeCamp", "owner": { "login": "freeCodeCamp", "id": 9892522, "avatar_url": "https://avatars0.githubusercontent.com/u/9892522?v=4", "gravatar_id": "", "url": "https://api.github.com/users/freeCodeCamp", "site_admin": false }, "private": false, "html_url": "https://github.com/freeCodeCamp/freeCodeCamp", "description": "The https://freeCodeCamp.org open source codebase "+ "and curriculum. Learn to code and help nonprofits.", // more omitted information }, //... ] }
Abordarea lui React
React face foarte simplu să scrieți elemente HTML în pagină și este una dintre caracteristicile pe care mi-am dorit întotdeauna să le am în timp ce scriu componente în JavaScript pur. React folosește JSX, care este foarte asemănător cu HTML obișnuit.
Cu toate acestea, nu asta citește browserul.
Sub capotă, React transformă JSX într-o grămadă de apeluri la o funcție React.createElement
. Să aruncăm o privire la un exemplu de JSX folosind un articol din API-ul GitHub și să vedem în ce se traduce.
<div className="repository"> <div>{item.name}</div> <p>{item.description}</p> <img src={item.owner.avatar_url} /> </div>;
; React.createElement( "div", { className: "repository" }, React.createElement( "div", null, item.name ), React.createElement( "p", null, item.description ), React.createElement( "img", { src: item.owner.avatar_url } ) );
JSX este foarte simplu. Scrieți cod HTML obișnuit și injectați date din obiect adăugând paranteze. Codul JavaScript dintre paranteze va fi executat, iar valoarea va fi inserată în DOM-ul rezultat. Unul dintre avantajele JSX este că React creează un DOM virtual (o reprezentare virtuală a paginii) pentru a urmări modificările și actualizările. În loc să rescrie întregul HTML, React modifică DOM-ul paginii ori de câte ori informațiile sunt actualizate. Aceasta este una dintre principalele probleme pe care React a fost creat pentru a le rezolva.
abordarea jQuery
Dezvoltatorii obișnuiau să folosească jQuery foarte mult. Aș dori să-l menționez aici pentru că este încă popular și, de asemenea, pentru că este destul de aproape de soluția în JavaScript pur. jQuery primește o referință la un nod DOM (sau o colecție de noduri DOM) interogând DOM. De asemenea, include acea referință cu diferite funcționalități pentru modificarea conținutului său.
În timp ce jQuery are propriile instrumente de construcție DOM, lucrul pe care îl văd cel mai des în mod natural este doar concatenarea HTML. De exemplu, putem insera cod HTML în nodurile selectate apelând funcția html()
. Conform documentației jQuery, dacă dorim să schimbăm conținutul unui nod div
cu demo-container
al clasei, putem face acest lucru:
$( "div.demo-container" ).html( "<p>All new content.<em>You bet!</em></p>" );
Această abordare facilitează crearea elementelor DOM; cu toate acestea, atunci când trebuie să actualizăm nodurile, trebuie să interogăm nodurile de care avem nevoie sau (mai frecvent) să revenim la recrearea întregului fragment de fiecare dată când este necesară o actualizare.
Abordarea DOM API
JavaScript în browsere are un API DOM încorporat care ne oferă acces direct la crearea, modificarea și ștergerea nodurilor dintr-o pagină. Acest lucru se reflectă în abordarea React și, prin utilizarea API-ului DOM, ne apropiem cu un pas de beneficiile acestei abordări. Modificăm doar elementele paginii care necesită de fapt să fie schimbate. Cu toate acestea, React ține evidența unui DOM virtual separat. Comparând diferențele dintre DOM-ul virtual și cel real, React este apoi capabil să identifice ce părți necesită modificare.
Acești pași suplimentari sunt uneori utili, dar nu întotdeauna, iar manipularea directă a DOM-ului poate fi mai eficientă. Putem crea noi noduri DOM folosind funcția _document.createElement_
, care va returna o referință la nodul creat. Urmărirea acestor referințe ne oferă o modalitate ușoară de a modifica doar nodurile care conțin partea care trebuie actualizată.
Folosind aceeași structură și sursă de date ca în exemplul JSX, putem construi DOM-ul în felul următor:
var item = document.createElement('div'); item.className = 'repository'; var nameNode = document.createElement('div'); nameNode.innerHTML = item.name item.appendChild(nameNode); var description = document.createElement('p'); description.innerHTML = item.description; item.appendChild(description ); var image = new Image(); Image.src = item.owner.avatar_url; item.appendChild(image); document.body.appendChild(item);
Dacă singurul lucru pe care îl aveți în vedere este eficiența execuției codului, atunci această abordare este foarte bună. Cu toate acestea, eficiența nu se măsoară doar în viteza de execuție, ci și în ușurința întreținerii, scalabilitate și plasticitate. Problema cu această abordare este că este foarte pronunțată și uneori complicată. Trebuie să scriem o grămadă de apeluri de funcții chiar dacă doar construim o structură de bază. Al doilea mare dezavantaj este numărul mare de variabile create și urmărite. Să presupunem că o componentă pe care o lucrați conține 30 de elemente DOM proprii, va trebui să creați și să utilizați 30 de elemente și variabile DOM diferite. Puteți să refolosiți unele dintre ele și să faceți niște jongleri în detrimentul mentenanței și al plasticității, dar poate deveni foarte dezordonat, foarte repede.

Un alt dezavantaj semnificativ se datorează numărului de linii de cod pe care trebuie să le scrieți. Cu timpul, devine din ce în ce mai greu să muți elemente de la un părinte la altul. Acesta este un lucru pe care îl apreciez cu adevărat de la React. Pot vedea sintaxa JSX și pot afla în câteva secunde ce nod este conținut, unde și pot schimba dacă este necesar. Și, deși ar putea părea că nu este o afacere mare la început, majoritatea proiectelor au schimbări constante care te vor face să cauți o cale mai bună.
Soluția propusă
Lucrul direct cu DOM funcționează și duce treaba la bun sfârșit, dar face și construirea paginii foarte pronunțată, mai ales când trebuie să adăugăm atribute HTML și noduri de cuib. Deci, ideea ar fi să surprindem unele dintre beneficiile lucrului cu tehnologii precum JSX și să ne simplificăm viața. Avantajele pe care încercăm să le reproducem sunt următoarele:
- Scrieți codul în sintaxa HTML, astfel încât crearea elementelor DOM să devină ușor de citit și modificat.
- Deoarece nu folosim un echivalent al DOM virtual ca în cazul React, trebuie să avem o modalitate ușoară de a indica și de a urmări nodurile care ne interesează.
Iată o funcție simplă care ar realiza acest lucru folosind un fragment HTML.
Browser.DOM = function (html, scope) { // Creates empty node and injects html string using .innerHTML // in case the variable isn't a string we assume is already a node var node; if (html.constructor === String) { var node = document.createElement('div'); node.innerHTML = html; } else { node = html; } // Creates of uses and object to which we will create variables // that will point to the created nodes var _scope = scope || {}; // Recursive function that will read every node and when a node // contains the var attribute add a reference in the scope object function toScope(node, scope) { var children = node.children; for (var iChild = 0; iChild < children.length; iChild++) { if (children[iChild].getAttribute('var')) { var names = children[iChild].getAttribute('var').split('.'); var obj = scope; while (names.length > 0) { var _property = names.shift(); if (names.length == 0) { obj[_property] = children[iChild]; } else { if (!obj.hasOwnProperty(_property)){ obj[_property] = {}; } obj = obj[_property]; } } } toScope(children[iChild], scope); } } toScope(node, _scope); if (html.constructor != String) { return html; } // If the node in the highest hierarchy is one return it if (node.childNodes.length == 1) { // if a scope to add node variables is not set // attach the object we created into the highest hierarchy node // by adding the nodes property. if (!scope) { node.childNodes[0].nodes = _scope; } return node.childNodes[0]; } // if the node in highest hierarchy is more than one return a fragment var fragment = document.createDocumentFragment(); var children = node.childNodes; // add notes into DocumentFragment while (children.length > 0) { if (fragment.append){ fragment.append(children[0]); }else{ fragment.appendChild(children[0]); } } fragment.nodes = _scope; return fragment; }
Ideea este simplă, dar puternică; trimitem funcției HTML-ul pe care vrem să-l creăm ca șir, în șirul HTML adăugăm un atribut var la nodurile pe care dorim să avem referințe create pentru noi. Al doilea parametru este un obiect în care vor fi stocate acele referințe. Dacă nu este specificat, vom crea o proprietate „noduri” pe nodul returnat sau pe fragmentul de document (în cazul în care cel mai înalt nod al ierarhiei este mai mult de unul). Totul se realizează în mai puțin de 60 de linii de cod.
Funcția funcționează în trei pași:
- Creați un nou nod gol și utilizați innerHTML în acel nou nod pentru a crea întreaga structură DOM.
- Iterați peste noduri și, dacă există atributul var, adăugați o proprietate în obiectul scope care indică nodul cu acel atribut.
- Returnați nodul de sus în ierarhie sau un fragment de document în cazul în care există mai multe.
Deci, cum arată acum codul nostru pentru redarea exemplului?
var UI = {}; var template = ''; template += '<div class="repository">' template += ' <div var="name"></div>'; template += ' <p var="text"></p>' template += ' <img var="image"/>' template += '</div>'; var item = Browser.DOM(template, UI); UI.name.innerHTML = data.name; UI.text.innerHTML = data.description; UI.image.src = data.owner.avatar_url;
Mai întâi, definim obiectul (UI) unde vom stoca referințele la nodurile create. Compunem apoi șablonul HTML pe care îl vom folosi, ca șir, marcând nodurile țintă cu un atribut „var”. După aceea, numim funcția Browser.DOM cu șablonul și obiectul gol care va stoca referințele. În cele din urmă, folosim referințele stocate pentru a plasa datele în interiorul nodurilor.
Această abordare separă, de asemenea, construirea structurii DOM și inserarea datelor în pași separați, ceea ce ajută la menținerea codului organizat și bine structurat. Acest lucru ne permite să creăm separat structura DOM și să completăm (sau să actualizăm) datele atunci când acestea devin disponibile.
Concluzie
Deși unora dintre noi nu le place ideea de a trece la cadre și de a preda controlul, este important să recunoaștem beneficiile pe care le aduc aceste cadre. Există un motiv pentru care sunt atât de populare.
Și în timp ce un cadru s-ar putea să nu se potrivească întotdeauna stilului sau nevoilor dvs., unele funcționalități și tehnici pot fi adoptate, emulate sau uneori chiar decuplate de un cadru. Unele lucruri vor fi întotdeauna pierdute în traducere, dar multe pot fi câștigate și utilizate cu o mică parte din costul pe care îl suportă un cadru.