ABONAMENTE VIDEO REDACȚIA
RO
EN
NOU
Numărul 152
Numărul 151 Numărul 150 Numărul 149 Numărul 148 Numărul 147 Numărul 146 Numărul 145 Numărul 144 Numărul 143 Numărul 142 Numărul 141 Numărul 140 Numărul 139 Numărul 138 Numărul 137 Numărul 136 Numărul 135 Numărul 134 Numărul 133 Numărul 132 Numărul 131 Numărul 130 Numărul 129 Numărul 128 Numărul 127 Numărul 126 Numărul 125 Numărul 124 Numărul 123 Numărul 122 Numărul 121 Numărul 120 Numărul 119 Numărul 118 Numărul 117 Numărul 116 Numărul 115 Numărul 114 Numărul 113 Numărul 112 Numărul 111 Numărul 110 Numărul 109 Numărul 108 Numărul 107 Numărul 106 Numărul 105 Numărul 104 Numărul 103 Numărul 102 Numărul 101 Numărul 100 Numărul 99 Numărul 98 Numărul 97 Numărul 96 Numărul 95 Numărul 94 Numărul 93 Numărul 92 Numărul 91 Numărul 90 Numărul 89 Numărul 88 Numărul 87 Numărul 86 Numărul 85 Numărul 84 Numărul 83 Numărul 82 Numărul 81 Numărul 80 Numărul 79 Numărul 78 Numărul 77 Numărul 76 Numărul 75 Numărul 74 Numărul 73 Numărul 72 Numărul 71 Numărul 70 Numărul 69 Numărul 68 Numărul 67 Numărul 66 Numărul 65 Numărul 64 Numărul 63 Numărul 62 Numărul 61 Numărul 60 Numărul 59 Numărul 58 Numărul 57 Numărul 56 Numărul 55 Numărul 54 Numărul 53 Numărul 52 Numărul 51 Numărul 50 Numărul 49 Numărul 48 Numărul 47 Numărul 46 Numărul 45 Numărul 44 Numărul 43 Numărul 42 Numărul 41 Numărul 40 Numărul 39 Numărul 38 Numărul 37 Numărul 36 Numărul 35 Numărul 34 Numărul 33 Numărul 32 Numărul 31 Numărul 30 Numărul 29 Numărul 28 Numărul 27 Numărul 26 Numărul 25 Numărul 24 Numărul 23 Numărul 22 Numărul 21 Numărul 20 Numărul 19 Numărul 18 Numărul 17 Numărul 16 Numărul 15 Numărul 14 Numărul 13 Numărul 12 Numărul 11 Numărul 10 Numărul 9 Numărul 8 Numărul 7 Numărul 6 Numărul 5 Numărul 4 Numărul 3 Numărul 2 Numărul 1
×
▼ LISTĂ EDIȚII ▼
Numărul 152
Abonamente

Signals: cum a evoluat reactivitatea în ecosistemul JavaScript

Florin Tomozei
Founder & Software Engineer @ Light Byte Studio



PROGRAMARE

Întotdeauna am fost curios să înțeleg cum funcționează lucrurile. Ca programator, atunci când folosești librării și frameworkuri noi, poate deveni destul de confuz tot procesul, pentru că nu știi exact cum funcționează lucrurile "în culise". Astfel că frameworkuri precum Django și Angular mi-au dat întotdeauna impresia că ceva "magic" se întâmplă fără ca eu să știu. În ultimii ani, am început să fiu curios referitor la cum funcționează mai exact lucrurile în aceste frameworkuri, pe care le folosim zi de zi. Spre exemplu, ce se întâmplă cu template-urile interpolate sau JSX și cum sunt convertite în HTML? Cum funcționează variabilele reactive, și ce înseamnă mai exact Virtual DOM?

Prin urmare, am ajuns să mă interesez despre modul în care reactivitatea a devenit o componentă necesară în ecosistemul JavaScript, despre modul în care a evoluat și despre direcția în care se îndreaptă momentan.

Istoric sumar

Dacă ne uităm puțin la trecutul limbajului și la modul în care a evoluat acesta, putem să observăm niște tipare care au existat din 1995 și până azi, care au influențat apariția conceptului Signals.

La începutul anilor 2000, totul era server-side. Limbajele compilate sau dinamice erau în vogă, alături de frameworkurile lor specializate pentru web. PHP și Symphony, ASP.NET în C#, Servlet și Spring pentru Java, Rails în Ruby sau Django pentru Python. Iar, în acele vremuri, JavaScript nu era privit la fel cum este privit astăzi. Era considerat mai mult un 'limbaj de jucărie", folosit pentru widgeturi mici și izolate, sau interacțiuni customizate după ce pagina era servită de către server. Nu existau module, nu exista NPM, iar toate variabilele erau globale.

Apoi, am avut parte de concepte precum XHR, Ajax și Google Web Toolkit care au permis librăriilor precum Dojo, Mootools și JQuery să prindă viață și să ajute programatorii prin interacțiuni simplificate ale DOM-ului și extensii aduse limbajului. În 2007 avem parte de apariția smarthphone-urilor, și astfel apare și nevoie de separare dintre client și server.

Marea revoluție a librăriilor vine cu nume mari, precum Knockout, Backbone și Angular.js, care apar cam în aceeași perioadă, fiind urmate la câțiva ani de React și Vue. Le numim librării, pentru că încercau să facă un singur lucru și să îl facă bine, respectiv să rezolve problemele de rendering pe care programatorii JavaScript le aveau în acea perioadă. Astfel, le putem privi ca și niște view-layer libraries, ușor de implementat într-un proiect deja existent pe care puteai apoi să îl convertești incremental pentru a folosi aceste noi tehnologii.

Urmează revoluția frameworkurilor care aduc suport pe mai multe paliere decât cel de rendering, oferind full-stack support alături de routing, server-side rendering, data management, modularitate, extensibilitate și management de dependințe etc. Amintim câteva: Angular, Next, Nuxt, SvelteKit, Remix, Astro, Quick, iar lista poate să continue.

Signals

Apoi, în 2020, SolidJS vine cu un conceptul de Signals. Și încet dar sigur, toată lumea începe să îl adopte și să îl promoveze ca noul mod de facto de a face state management. Vue îl va numi ref, Angular, Preact și Qwik îl implementează ca signal, iar Svelte îi va folosi magia prin intermediul $runes. Practic, toată lumea își dă seama că Knockout a avut dreptate acum 15 ani când a implementat același tip de reactivitate fine-grained prin observable și computed.

Și, totuși, ce sunt aceste Signals?

Pe scurt, un Signal este o primitivă reactivă, folosită pentru managementul state-ului unei aplicații. Ca inspirație, pornește de la un Design Pattern, respectiv cel de Observer, prin care oferă un API simplificat bazat pe un set de principii (features) de reactivitate. Astfel permite o experiență de development ergonomică, alături de o implementare bine optimizată pentru procesul de rendering.

API

API-ul este format din trei părți:

Principii de reactivitate

  1. Dependency tracking - abilitatea de a putea urmări dependințele între state-uri. Aceasta trebuie să fie optimizată și automată, rezultând într-un grafic de dependințe care reprezintă automat toate legăturile realizate.

  2. Lazy by default - Dacă un copac cade în pădure, și nimeni nu l-a auzit căzând, face zgomot? Astfel, dacă un state nu este legat în nici un fel de alte state-uri sau efecte, acesta nu face parte din graficul de dependințe, deci nu trebuie urmărit. De asemenea, state-ul derivat este evaluat doar atunci când o dependință este invocată și nu în momentul în care este declarat.

  3. Memorization - Pe scurt, ultima valoare a unui state este cache-uită. Dacă nu și-a schimbat valoarea, o să știm întotdeauna care este valoarea actuală.

  4. Push-then-Pull - principiul pe care se bazează toată ideea semnalelor. Dacă este să ne uităm la Observer Pattern, acesta este de tip Push, deoarece subiectul întotdeauna își notifică observatorii de schimbările de state (subject pushes notifications). Pull based ar însemna că acei care depind de un subiect ar cere ei valoarea. Atunci, Push-then-Pull ar fi capabilitatea prin care Signalul trimite o notificare prin care zice că i s-a schimbat valoarea, apoi toate dependințele lui au responsabilitatea să reacționeze "by pulling the value". În cazul în care există mai multe dependințe care se schimbă în același timp, se parcurge graficul de dependințe și se iau noile valori, ca mai apoi să se producă pasul de rendering.

Aceste principii stau la baza implementării semnalelor, iar prin folosirea lor rezultă următoarele "core features":

  1. Fine Grained Reactivity - evităm recalculări și randări care nu sunt necesare. Astfel experiența utilizatorului este îmbunătățită și optimizată.

  2. Glitch-free Rendering - oferă o propagare consistentă a valorilor, evitând valorile intermediare atunci când multiple valori se modifică în același timp.

Implementare

Mai jos să găsim un exemplu rudimentar prin care implementăm în câteva linii de cod conceptul de Signal. Acesta oferă o funcție numită signal pentru crearea de root state care ține o valoare, și o funcție de effect pentru crearea zonelor de cod care răspund ca efect al schimbării valorilor unui state.

const context = [];
function signal(value) {
 const subscriptions = new Set();
  const read = () => {
    const observer = context[context.length - 1];
    if (observer) subscriptions.add(observer);
    return value;
  };
  const write = (newValue) => {
    value = newValue;
    for (const observer of subscriptions) {
      observer.execute();
    }
  };
  return [read, write];
}

function effect(fn) {
  const effect = {
    execute() {
      context.push(effect);
      fn();
      context.pop();
    }
  };
  effect.execute();
}

Dacă ar fi să vedem aceste funcții în acțiune, într-o pagină simplă de HTML, ar arăta în felul următor:

<h1 id="hook"></h1>
<button onclick="increment()">Increment</button>
<script>
    const [data, setData] = signal(1);
    effect(() => {
      document.querySelector("#hook").textContent = data();
    });
    const increment = () => setData(data() + 1);
</script>

TC39 Proposal

Adoptând Signals în majoritatea framework-urilor moderne, ne-am dat seama că sunt o idee bună, and we should do more of that. Astfel, în momentul de față există o propunere înaintată spre TC39 prin care campionii propunerii vor să introducă conceptul de Signals ca și un standard al limbajului JavaScript. Momentan, propunerea este de abia în primul stadiu (Stage 1), deci mai este cale lungă până va ajunge ca implementare în runtime, dar propunerea este bine scrisă și interesantă, deoarece își dorește să aducă un API pentru managementul state-ului reactiv la nivel de limbaj. De asemenea, propunerea este scrisă cu ideea ca frameworkurile să poată ulterior să construiască peste acest API, oferind interoperabilitate și suport pentru graficele de dependințe și mecanismul de auto-tracking, astfel încât frameworkul să poată să își rețină modul personal de implementare.

În concluzie

Viitorul sună bine! Cam toate frameworkurile moderne implementează în momentul de față o primitivă reactivă care se bazează pe conceptul de Signal, oferind programatorului ergonomie și simplitate, atunci când vine vorba de state management. Lucrurile nu se opresc aici, fiecare framework dezvoltă în continuare funcționalitatea proprie și aduce feature-uri noi. Prin propunerea de standardizare a Signalurilor la nivel de limbaj, putem să privim cu încredere la un viitor apropiat în care problema de state management nu va da așa de multe bătăi de cap dezvoltatorilor de aplicații din ecosistemul JavaScript.

Conferință TSM

NUMĂRUL 150 - Technologiile SAP ABAP

Sponsori

  • Accenture
  • BT Code Crafters
  • Bosch
  • Betfair
  • MHP
  • BoatyardX
  • .msg systems
  • P3 group
  • Ing Hubs
  • Cognizant Softvision
  • Colors in projects