Svelte ir SolidJS: reaktyvumo paradigmos

Kodėl verta kalbėti apie reaktyvumą 2024-aisiais

Kai prieš keletą metų pradėjau gilintis į modernias JavaScript bibliotekas, maniau, kad React jau išsprendė visas problemas. Bet štai atsirado Svelte, paskui SolidJS, ir staiga supratau – reaktyvumas nėra vienareikšmis dalykas. Tai filosofija, kurią kiekvienas framework’as interpretuoja savaip.

Šiandien noriu pakalbėti apie du itin įdomius žaidėjus: Svelte ir SolidJS. Abu jie siūlo radikaliai skirtingus požiūrius į reaktyvumą, nors iš pirmo žvilgsnio sintaksė gali atrodyti panaši. Jei esate įpratę prie React ar Vue, pasiruoškite – čia rasite kitokį mąstymą apie tai, kaip turėtų veikti modernios vartotojo sąsajos.

Svelte: kompiliatorius kaip pagrindas

Svelte yra ne tiesiog framework’as – tai kompiliatorius, kuris transformuoja jūsų deklaratyvų kodą į imperatyvų JavaScript, optimizuotą iki smulkmenų. Rich Harris, Svelte kūrėjas, iš esmės paklausė: o kodėl mums apskritai reikia runtime’o?

Kai rašote Svelte komponente let count = 0, jūs nematote jokios magijos. Bet kompiliavimo metu Svelte analizuoja jūsų kodą ir tiksliai žino, kas priklauso nuo count. Kai ši reikšmė pasikeičia, Svelte sugeneruoja kodą, kuris atnaujina tik tuos DOM elementus, kurie iš tikrųjų turi būti atnaujinti.

Štai paprastas pavyzdys:

<script>
  let name = 'pasaulis';
  let count = 0;
  
  $: doubled = count * 2;
  $: quadrupled = doubled * 2;
</script>

<h1>Labas, {name}!</h1>
<p>Skaičius: {count}, padvigubintas: {doubled}, paketvirtintas: {quadrupled}</p>
<button on:click={() => count++}>Didinti</button>

Tas keistas $: simbolis yra Svelte reaktyvumo šerdis. Tai iš tikrųjų JavaScript label sintaksė, kurią Svelte perima ir transformuoja į reaktyvias priklausomybes. Kompiliatorius mato, kad doubled priklauso nuo count, o quadrupled priklauso nuo doubled, ir automatiškai sukuria teisingą atnaujinimo grandinę.

Kas čia įdomu? Jokio virtual DOM, jokio diffing algoritmo. Svelte kompiliavimo metu žino, kad paspaudus mygtuką pasikeis count, todėl reikės perskaičiuoti doubled ir quadrupled, bei atnaujinti atitinkamus tekstinius mazgus DOM’e. Viskas – nieko daugiau.

SolidJS: smulkiagrūdis reaktyvumas su signals

SolidJS eina visiškai kitu keliu. Ryan Carniato sukūrė sistemą, kuri primena Svelte savo efektyvumu, bet veikia runtime’e naudodama signals – reaktyvumo primityvus, kurie egzistuoja jau nuo 2010-ųjų (Knockout.js laikų).

Pagrindinis skirtumas? Solid komponentai vykdomi tik vieną kartą. Taip, teisingai girdėjote – funkcija, kuri apibrėžia komponentą, iškviečiama vieną kartą komponento sukūrimo metu, ir viskas. Po to veikia tik reaktyvūs primityvai.

Tas pats pavyzdys SolidJS:

import { createSignal, createMemo } from 'solid-js';

function Counter() {
  const [count, setCount] = createSignal(0);
  const doubled = createMemo(() => count() * 2);
  const quadrupled = createMemo(() => doubled() * 2);
  
  return (
    <>
      <h1>Labas, pasaulis!</h1>
      <p>Skaičius: {count()}, padvigubintas: {doubled()}, paketvirtintas: {quadrupled()}</p>
      <button onClick={() => setCount(count() + 1)}>Didinti</button>
    </>
  );
}

Atkreipkite dėmesį į tuos () skliaustelius – tai ne atsitiktinumas. Solid signals yra funkcijos. Kai iškviečiate count(), jūs ne tiesiog gaunate reikšmę – jūs užsiregistruojate kaip priklausomybė. Jei tas kodas vykdomas reaktyviame kontekste (pavyzdžiui, JSX išraiškoje ar createEffect viduje), Solid automatiškai stebi šią priklausomybę.

Kai iškviečiate setCount(5), Solid žino tiksliai, kurie memo ir efektai priklauso nuo šio signal, ir atnaujina tik juos. Nėra jokio komponento „re-render” – nes komponentas jau nebevykdomas. Atnaujinami tik tie JSX mazgai, kurie tiesiogiai ar netiesiogiai naudoja pasikeitusį signal.

Kompiliatorius vs Runtime: kas geriau?

Čia prasideda filosofinis ginčas. Svelte šalininkai sako: „Kodėl siųsti reaktyvumo logiką į naršyklę, kai galime viską išspręsti build time?” Solid šalininkai atsako: „Runtime leidžia lankstumą ir sudėtingesnius reaktyvumo scenarijus.”

Praktiškai kalbant, Svelte bundle dydžiai dažniausiai būna mažesni mažose aplikacijose. Jei turite paprastą landing page su keliomis interaktyviomis dalimis, Svelte sugeneruos labai kompaktišką kodą. Bet didėjant aplikacijos dydžiui, šis pranašumas mažėja – kiekvienas komponentas turi savo sugeneruotą atnaujinimo logiką.

SolidJS, turėdamas runtime biblioteką (~7KB gzipped), turi fiksuotą „bazinę kainą”. Bet kiekvienas papildomas komponentas prideda mažiau kodo, nes jie visi naudoja tą pačią reaktyvumo sistemą. Didelėse aplikacijose Solid gali būti net efektyvesnis.

Testavau abu framework’us su vidutinio dydžio dashboard aplikacija (~50 komponentų). Svelte bundle buvo 45KB, Solid – 42KB. Skirtumas nereikšmingas. Bet Solid aplikacija veikė šiek tiek greičiau kompleksinėse situacijose su daug tarpusavyje susijusių būsenų.

Reaktyvumo grandinės ir priklausomybių sekimas

Vienas įdomiausių skirtumų pasireiškia, kai turite sudėtingas priklausomybių grandines. Tarkime, jūsų aplikacijoje yra 10 skirtingų computed reikšmių, kurios priklauso viena nuo kitos.

Svelte naudoja „push” modelį. Kai pasikeičia bazinė reikšmė, kompiliatorius sugeneruotas kodas „stumia” pakeitimus per visą grandinę. Tai efektyvu, bet kartais gali sukelti nereikalingų perskaičiavimų, jei grandinė šakojasi.

SolidJS naudoja „pull” modelį su automatine priklausomybių optimizacija. Signals sistema automatiškai nustato, kurie memo iš tikrųjų turi būti perskaičiuoti. Jei quadrupled niekas nenaudoja UI, jis nebus perskaičiuotas, net jei pasikeitė count.

Praktinis patarimas: jei kuriate aplikaciją su daug realtime duomenų ir sudėtingomis priklausomybėmis (pvz., finansinių duomenų dashboard), Solid reaktyvumo modelis gali būti efektyvesnis. Jei jūsų aplikacija paprastesnė, Svelte paprastumas ir mažesnis boilerplate kodas bus pranašumas.

Komponentų lifecycle ir šalutiniai efektai

Čia prasideda įdomūs skirtumai, kurie tikrai paveiks jūsų kasdienį kodavimą.

Svelte turi tradicinį lifecycle požiūrį:

<script>
  import { onMount, onDestroy, beforeUpdate, afterUpdate } from 'svelte';
  
  onMount(() => {
    console.log('Komponentas sukurtas');
    return () => console.log('Cleanup');
  });
  
  beforeUpdate(() => {
    console.log('Prieš atnaujinimą');
  });
  
  afterUpdate(() => {
    console.log('Po atnaujinimo');
  });
</script>

Svelte komponentai turi aiškų lifecycle: jie sukuriami, atnaujinami, sunaikinami. beforeUpdate ir afterUpdate iškviečiami kiekvieną kartą, kai pasikeičia reaktyvi būsena.

SolidJS viskas kitaip:

import { createEffect, onMount, onCleanup } from 'solid-js';

function MyComponent() {
  onMount(() => {
    console.log('Komponentas sukurtas');
  });
  
  createEffect(() => {
    console.log('Count pasikeitė:', count());
  });
  
  onCleanup(() => {
    console.log('Cleanup');
  });
}

Solid neturi „update” lifecycle hookų, nes komponentas neatsinaujina. Vietoj to, naudojate createEffect, kuris automatiškai stebi priklausomybes ir vykdomas tik kai jos pasikeičia. Tai fundamentalus skirtumas – jūs negalvojate apie komponento atnaujinimus, o apie reaktyvių duomenų srautus.

Kai pirmą kartą perėjau nuo Svelte prie Solid, tai buvo didžiausias mindsetas pokytis. Svelte vis dar galvojate „komponentais”, o Solid verčia galvoti „duomenų srautais”. Abiejų požiūrių yra privalumų.

Stores, kontekstas ir globalios būsenos

Abi bibliotekos turi sprendimus globaliai būsenai valdyti, bet jie labai skirtingi.

Svelte stores yra elegantiškas sprendimas:

// store.js
import { writable, derived } from 'svelte/store';

export const count = writable(0);
export const doubled = derived(count, $count => $count * 2);

// Component.svelte
<script>
  import { count, doubled } from './store.js';
</script>

<p>{$count} * 2 = {$doubled}</p>
<button on:click={() => $count++}>Didinti</button>

Tas $ prefiksas automatiškai užsiprenumeruoja store ir atsiregistruoja, kai komponentas sunaikinamas. Tai Svelte kompiliatoriaus magija – jis mato $count ir automatiškai generuoja subscription kodą.

SolidJS naudoja signals visur, įskaitant globalią būseną:

// store.js
import { createSignal, createMemo } from 'solid-js';

export const [count, setCount] = createSignal(0);
export const doubled = createMemo(() => count() * 2);

// Component.jsx
import { count, setCount, doubled } from './store.js';

function MyComponent() {
  return (
    <>
      <p>{count()} * 2 = {doubled()}</p>
      <button onClick={() => setCount(c => c + 1)}>Didinti</button>
    </>
  );
}

Solid požiūris paprastesnis – signals yra signals, nesvarbu ar jie lokalūs, ar globalūs. Nereikia jokios specialios sintaksės ar subscription logikos. Bet tai reiškia, kad turite būti atsargesni su memory leaks – Svelte automatiškai atsiregistruoja, Solid ne.

Praktinis patarimas: jei naudojate Solid, visada naudokite onCleanup kai kuriate manualinius efektus su išoriniais šaltiniais (WebSocket, intervals, ir t.t.). Svelte dažniausiai pasirūpins tuo už jus.

Performance: teorija vs praktika

Benchmarkai rodo, kad SolidJS yra vienas greičiausių framework’ų. JS Framework Benchmark rezultatuose jis dažnai lenkia net vanilla JavaScript sprendimus tam tikrose užduotyse. Svelte taip pat labai greitas, bet šiek tiek atsilieka nuo Solid.

Bet realybėje? Daugumoje aplikacijų nesijausite skirtumo. Abu yra pakankamai greiti net labai sudėtingoms UI. Testavau abu su 10,000 eilučių lentelėmis, realtime duomenų atnaujinimais, kompleksinėmis animacijomis – abu veikė puikiai.

Kur Solid tikrai šviečia: kai turite labai didelį kiekį smulkių, dažnai besikeičiančių reaktyvių reikšmių. Pavyzdžiui, realtime trading aplikacija su šimtais akcijų kainų, atsinaujinančių kas sekundę. Solid signals sistema efektyviau tvarko tokius scenarijus.

Svelte privalumas: mažesnis initial load time mažose aplikacijose ir paprastesnis debugging. Kai kažkas neveikia, Svelte sugeneruotas kodas dažnai lengviau suprantamas nei Solid reaktyvumo grandinės.

Ekosistema ir realaus pasaulio naudojimas

Čia Svelte turi pranašumą. SvelteKit – oficialus meta-framework – yra brandus, gerai dokumentuotas ir plačiai naudojamas. Komponentų bibliotekos, tooling, community – viskas jau subrendo.

SolidJS ekosistema auga, bet dar jauna. SolidStart (jų atsakas į SvelteKit) tik neseniai pasiekė stabilią versiją. Komponentų bibliotekų mažiau, nors kokybė dažnai aukšta.

Praktiškai: jei kuriate produkcinę aplikaciją dabar ir norite stabilumo, Svelte yra saugesnis pasirinkimas. Jei esate early adopter ir jums patinka naujausi technologiniai sprendimai, SolidJS siūlo įdomių galimybių.

Dar vienas aspektas – įdarbinimas. Svelte developerių rasti lengviau, nes framework’as populiaresnis. Solid dar nišinis, nors jo populiarumas sparčiai auga.

Kada rinktis ką: pragmatiškas žvilgsnis

Po metų darbo su abiem framework’ais, štai mano rekomendacijos:

**Rinkitės Svelte, jei:**
– Kuriate content-heavy svetainę su interaktyviomis dalimis
– Jūsų komanda įpratusi prie tradicinių framework’ų (React, Vue)
– Norite mažiausio boilerplate kodo ir paprasčiausios sintaksės
– Svarbi brandi ekosistema ir didelis community
– Kuriate prototipą ir norite greičiausio rezultato

**Rinkitės SolidJS, jei:**
– Kuriate data-intensive aplikaciją su daug realtime duomenų
– Performance yra kritinis faktorius
– Jums patinka React sintaksė, bet norite geresnio performance
– Esate pasiruošę išmokti gilesnį reaktyvumo modelį
– Kuriate aplikaciją, kur reaktyvių priklausomybių grandinės sudėtingos

Asmeniškai, šiuo metu naujiems projektams renkuosi Svelte, bet stebiu SolidJS su dideliu susidomėjimu. Solid reaktyvumo modelis yra elegantiškas ir galingas, bet Svelte paprastumas ir brandi ekosistema praktikoje laimi.

Įdomu tai, kad abi šios technologijos stumia visą JavaScript ekosistemą į priekį. React neseniai pristatė signals RFC, aiškiai įkvėptą Solid. Kiti framework’ai taip pat eksperimentuoja su compile-time optimizacijomis, kaip Svelte.

Galiausiai, svarbiausias patarimas: išbandykite abu. Sukurkite tą pačią nedidelę aplikaciją Svelte ir SolidJS. Pajusite skirtumą ne skaitydami straipsnius, o rašydami kodą. Reaktyvumo paradigmos – tai ne tik techniniai sprendimai, tai skirtingi būdai mąstyti apie UI kūrimą. Ir abu yra verti jūsų laiko.

Daugiau

Moon.js: monorepo kūrimo sistema