Ce rezolvă cadrele web și cum să faci fără ele (Partea 1)

Publicat: 2022-03-10
Rezumat rapid ↬ În acest articol, Noam Rosenthal se aprofundează în câteva caracteristici tehnice care sunt comune în cadrul cadrelor și explică modul în care unele dintre cadrele diferite le implementează și cât costă.

Recent, am devenit foarte interesat de compararea cadrelor cu JavaScript vanilla. A început după o anumită frustrare pe care am avut-o folosind React în unele dintre proiectele mele independente și odată cu cunoștința mea recentă și mai intimă cu standardele web ca editor de specificații.

Am fost interesat să văd care sunt punctele comune și diferențele dintre cadre , ce are de oferit platforma web ca alternativă mai slabă și dacă este suficientă. Obiectivul meu nu este să distrug cadrele, ci mai degrabă să înțeleg costurile și beneficiile, să stabilesc dacă există o alternativă și să văd dacă putem învăța din ea, chiar dacă decidem să folosim un cadru.

În această primă parte, voi aborda în profunzime câteva caracteristici tehnice comune în cadrul cadrelor și modul în care unele dintre cadrele diferite le implementează. Mă voi uita și la costul utilizării acestor cadre.

Cadrele

Am ales patru cadre pe care să le uit: React, fiind cel dominant astăzi și trei concurenți mai noi care pretind că fac lucrurile diferit de React.

  • Reacţiona
    „React face să fie nedureroasă crearea de interfețe interactive. Vizualizările declarative vă fac codul mai previzibil și mai ușor de depanat.”
  • SolidJS
    „Solid urmează aceeași filozofie ca și React... Cu toate acestea, are o implementare complet diferită, care renunță la utilizarea unui DOM virtual.”
  • Svelt
    „Svelte este o abordare radical nouă pentru construirea de interfețe cu utilizatorul... un pas de compilare care are loc atunci când vă construiți aplicația. În loc să folosească tehnici precum diferența DOM virtuală, Svelte scrie cod care actualizează chirurgical DOM-ul atunci când starea aplicației tale se schimbă.”
  • Lit
    „Construindu-se pe standardele componentelor web, Lit adaugă doar... reactivitate, șabloane declarative și o mână de caracteristici bine gândite.”

Pentru a rezuma ceea ce spun cadrele despre diferențiatorii lor:

  • React facilitează construirea interfețelor de utilizare cu vizualizări declarative.
  • SolidJS urmează filozofia lui React, dar folosește o tehnică diferită.
  • Svelte folosește o abordare în timp de compilare a interfețelor de utilizare.
  • Lit folosește standardele existente, cu unele caracteristici ușoare adăugate.

Ce cadre rezolvă

Cadrele în sine menționează cuvintele declarativ, reactivitate și DOM virtual. Să vedem ce înseamnă acestea.

Programare declarativă

Programarea declarativă este o paradigmă în care logica este definită fără a specifica fluxul de control. Descriem care trebuie să fie rezultatul, mai degrabă decât ce pași ne-ar duce acolo.

În primele zile ale cadrelor declarative, în jurul anului 2010, API-urile DOM erau mult mai simple și mai detaliate, iar scrierea de aplicații web cu JavaScript imperativ necesita mult cod standard. Atunci a devenit răspândit conceptul de „model-view-viewmodel” (MVVM), cu cadrele de atunci inovatoare Knockout și AngularJS, oferind un strat declarativ JavaScript care s-a ocupat de această complexitate în interiorul bibliotecii.

MVVM nu este un termen utilizat pe scară largă astăzi și este oarecum o variație a termenului mai vechi „legare de date”.

Legarea datelor

Legarea datelor este o modalitate declarativă de a exprima modul în care datele sunt sincronizate între un model și o interfață cu utilizatorul.

Toate cadrele UI populare oferă o anumită formă de legare a datelor, iar tutorialele lor încep cu un exemplu de legare a datelor.

Iată legarea datelor în JSX (SolidJS și React):

 function HelloWorld() { const name = "Solid or React"; return ( <div>Hello {name}!</div> ) }

Legarea datelor în Lit:

 class HelloWorld extends LitElement { @property() name = 'lit'; render() { return html`<p>Hello ${this.name}!</p>`; } }

Legarea datelor în Svelte:

 <script> let name = 'world'; </script> <h1>Hello {name}!</h1>

Reactivitate

Reactivitatea este o modalitate declarativă de a exprima propagarea schimbării.

Când avem o modalitate de a exprima în mod declarativ legarea datelor, avem nevoie de o modalitate eficientă pentru ca cadrul de a propaga modificări.

Motorul React compară rezultatul randării cu rezultatul anterior și aplică diferența DOM-ului însuși. Acest mod de a gestiona propagarea modificării se numește DOM virtual.

În SolidJS, acest lucru se face mai explicit, cu stocul și elementele încorporate. De exemplu, elementul Show ar ține evidența a ceea ce s-a schimbat intern, în locul DOM-ului virtual.

În Svelte, este generat codul „reactiv”. Svelte știe ce evenimente pot provoca o schimbare și generează un cod simplu care trasează linia dintre eveniment și modificarea DOM.

În Lit, reactivitatea este realizată folosind proprietățile elementului, bazându-se în esență pe reactivitatea încorporată a elementelor personalizate HTML.

Mai multe după săritură! Continuați să citiți mai jos ↓

Logică

Atunci când un cadru oferă o interfață declarativă pentru legarea datelor, cu implementarea sa de reactivitate, trebuie, de asemenea, să ofere o modalitate de a exprima o parte din logica care este scrisă în mod tradițional în mod imperativ. Blocurile de bază ale logicii sunt „dacă” și „pentru”, iar toate cadrele majore oferă o anumită expresie a acestor blocuri de construcție.

Condiționale

Pe lângă legarea datelor de bază, cum ar fi numere și șir, fiecare cadru furnizează o primitivă „condițională”. În React, arată astfel:

 const [hasError, setHasError] = useState(false); return hasError ? <label>Message</label> : null; … setHasError(true);

SolidJS oferă o componentă condiționată încorporată, Show :

 <Show when={state.error}> <label>Message</label> </Show>

Svelte oferă directiva #if :

 {#if state.error} <label>Message</label> {/if}

În Lit, ați folosi o operație ternară explicită în funcția de render :

 render() { return this.error ? html`<label>Message</label>`: null; }

Liste

Cealaltă primitivă de cadru comună este gestionarea listelor. Listele sunt o parte cheie a interfețelor de utilizare — lista de contacte, notificări etc. — și pentru a funcționa eficient, acestea trebuie să fie reactive, nu să actualizeze întreaga listă atunci când un element de date se modifică.

În React, gestionarea listelor arată astfel:

 contacts.map((contact, index) => <li key={index}> {contact.name} </li>)

React folosește atributul key special pentru a diferenția elementele din listă și se asigură că întreaga listă nu este înlocuită cu fiecare randare.

În SolidJS, sunt utilizate elementele încorporate for și index :

 <For each={state.contacts}> {contact => <DIV>{contact.name}</DIV> } </For>

Pe plan intern, SolidJS folosește propriul magazin împreună cu for și index pentru a decide ce elemente să actualizeze atunci când articolele se modifică. Este mai explicit decât React, permițându-ne să evităm complexitatea DOM-ului virtual.

Svelte folosește each directivă, care este transpilată pe baza actualizărilor sale:

 {#each contacts as contact} <div>{contact.name}</div> {/each}

Lit furnizează o funcție de repeat , care funcționează în mod similar cu maparea listei bazată pe key React:

 repeat(contacts, contact => contact.id, (contact, index) => html`<div>${contact.name}</div>`

Modelul componentelor

Un lucru care nu intră în domeniul de aplicare al acestui articol este modelul componentelor din diferitele cadre și modul în care poate fi tratat folosind elemente HTML personalizate.

Notă : Acesta este un subiect mare și sper să îl acopăr într-un articol viitor, deoarece acesta ar fi prea lung. :)

Costul

Cadrele oferă legături declarative de date, primitive ale fluxului de control (condiționale și liste) și un mecanism reactiv pentru propagarea modificărilor.

Ele oferă și alte lucruri majore, cum ar fi o modalitate de a reutiliza componente, dar acesta este un subiect pentru un articol separat.

Sunt cadrele utile? Da. Ele ne oferă toate aceste caracteristici convenabile. Dar aceasta este întrebarea corectă de pus? Utilizarea unui cadru are un cost. Să vedem care sunt aceste costuri.

Dimensiunea pachetului

Când mă uit la dimensiunea pachetului, îmi place să mă uit la dimensiunea minimizată non-Gzip. Aceasta este dimensiunea cea mai relevantă pentru costul CPU al execuției JavaScript.

  • ReactDOM are aproximativ 120 KB.
  • SolidJS are aproximativ 18 KB.
  • Lit este de aproximativ 16 KB.
  • Svelte are aproximativ 2 KB, dar dimensiunea codului generat variază.

Se pare că cadrele de astăzi fac o treabă mai bună decât React de a menține dimensiunea pachetului mică. DOM-ul virtual necesită mult JavaScript.

Construiește

Cumva ne-am obișnuit să „construim” aplicațiile noastre web. Este imposibil să porniți un proiect front-end fără a configura Node.js și un bundler, cum ar fi Webpack, care să se ocupe de unele modificări recente de configurare în pachetul de pornire Babel-TypeScript și tot acel jazz.

Cu cât este mai expresivă și cu cât dimensiunea pachetului cadrului este mai mică, cu atât este mai mare sarcina instrumentelor de construcție și timpul de transpilare.

Svelte susține că DOM-ul virtual este pur peste cap. Sunt de acord, dar poate că „construcția” (ca și în cazul Svelte și SolidJS) și motoarele de șabloane personalizate de partea clientului (ca și în cazul Lit) sunt, de asemenea, pur și simplu overhead, de alt fel?

Depanare

Odată cu construcția și transpilarea vine un alt tip de cost.

Codul pe care îl vedem atunci când folosim sau depanăm aplicația web este total diferit de ceea ce am scris. Acum ne bazăm pe instrumente speciale de depanare de diferite calități pentru a face inginerie inversă a ceea ce se întâmplă pe site și pentru a-l conecta cu erori în propriul nostru cod.

În React, stiva de apeluri nu este niciodată „a ta” — React se ocupă de programare pentru tine. Acest lucru funcționează excelent atunci când nu există erori. Dar încercați să identificați cauza re-redărilor în buclă infinită și veți fi într-o lume de durere.

În Svelte, dimensiunea pachetului bibliotecii în sine este mică, dar veți livra și veți depana o grămadă de coduri criptice generate, care este implementarea reactivității de către Svelte, personalizat la nevoile aplicației dvs.

Cu Lit, este mai puțin despre construirea, dar pentru a-l depana eficient trebuie să înțelegeți motorul său de șablon. Acesta ar putea fi cel mai mare motiv pentru care sentimentul meu față de cadre este sceptic.

Când căutați soluții declarative personalizate, ajungeți cu o depanare imperativă mai dureroasă. Exemplele din acest document folosesc Typescript pentru specificația API, dar codul în sine nu necesită transpilare.

Upgrade-uri

În acest document, m-am uitat la patru cadre, dar există mai multe cadre decât pot număra (AngularJS, Ember.js și Vue.js, pentru a numi câteva). Poți conta pe cadru, dezvoltatorii săi, partajarea minții și ecosistemul său să funcționeze pentru tine pe măsură ce evoluează?

Un lucru care este mai frustrant decât remedierea propriilor erori este nevoia de a găsi soluții pentru erorile cadru. Și un lucru care este mai frustrant decât erorile de cadru sunt erorile care apar atunci când actualizați un cadru la o versiune nouă fără a modifica codul.

Adevărat, această problemă există și în browsere, dar atunci când apare, se întâmplă tuturor și, în cele mai multe cazuri, o remediere sau o soluție publicată este iminentă. De asemenea, majoritatea modelelor din acest document se bazează pe API-uri de platforme web mature; nu este întotdeauna nevoie să mergem cu marginea sângerării.

rezumat

Ne-am scufundat puțin mai adânc în înțelegerea problemelor de bază pe care cadrele încearcă să le rezolve și a modului în care acestea le rezolvă, concentrându-ne pe legarea datelor, reactivitate, condiționale și liste. Ne-am uitat și la costuri.

În partea a 2-a, vom vedea cum aceste probleme pot fi abordate fără a folosi deloc un cadru și ce putem învăța din acesta. Rămâneți aproape!

Mulțumiri speciale următoarelor persoane pentru recenziile tehnice: Yehonatan Daniv, Tom Bigelajzen, Benjamin Greenbaum, Nick Ribal și Louis Lazaris.