Rune Svelte 5 i sygnały SolidJS: aktualizacje interfejsu bez ponownego renderowania
Zobacz, jak Svelte 5 kompiluje runy oraz jak SolidJS łączy sygnały w celu bezpośredniej aktualizacji DOM, jak różni się to od React i Angular oraz kiedy warto dokonać takiej zmiany.
Każdy programista używający React musi w pewnym momencie wyjaśniać, dlaczego komponent jest renderowany cztery razy, mimo że nic widzialnego się nie zmieniło, oraz dlaczego rozwiązanie wymaga użycia memo, tablicy zależności oraz stabilnego funkcji zwrotnej. Svelte i SolidJS wychodzą z innej premisy: jeśli framework dokładnie wie, który element stanu wpływa na dany element DOM, może zaktualizować właśnie ten węzeł i całkowicie uniknąć ponownego uruchamiania komponentów. Ten artykuł wyjaśnia, w jaki sposób każdy z tych frameworków to osiąga, jak wygląda kod w praktyce, gdzie ich model różni się od React i Angular oraz jak zdecydować, który z nich nadaje się do Twojego następnego projektu.
Wirtualny DOM był środkiem, a nie celem
Kluczową koncepcją Reacta przy jego wprowadzeniu w 2013 roku była wirtualna DOM. Interfejs użytkownika opisuje się tu jako funkcja stanu. Gdy stan ulega zmianie, React ponownie wywołuje komponent, tworzy nowe drzewo w pamięci, porównuje je z poprzednim i aplikuje tylko różnice do rzeczywistej DOM.
Taki projekt sprawił, że interfejsy stały się znacznie bardziej przewidywalne w porównaniu z ręczną manipulacją DOM, i nadal stanowi solidny model. Ma on jednak swoje koszty – funkcja komponentu jest wykonywana ponownie niezależnie od tego, czy jej wynik musi ulec zmianie, przydzielane jest nowe drzewo, a proces porównywania określa, co faktycznie się zmieniło, wszystko to po to, by w najgorszym przypadku zaktualizować pojedynczy <span>. Jeśli chcesz poznać szczegóły dotyczące tego, co porównuje mechanizm synchronizacji i dlaczego, artykuł na temat jak działa porównywanie wirtualnej DOM omawia te kwestie.
Znaczna część interfejsu API React od tamtej pory – takie funkcje jak memo, useMemo, useCallback oraz najnowszy React Compiler – ma na celu uniknięcie obliczeń, które w przeciwnym razie musiałby wykonać model renderowania i porównywania. Kompilator automatyzuje proces memoizacji, dzięki czemu programiści muszą ręcznie pisać mniej kodu, ale podstawowy model pozostaje niezmieniony: komponenty są ponownie uruchamiane, a optymalizacja polega na przekonaniu ich, by tego nie robiły. Artykuł na temat o tym, co optymalizuje React Compiler i co pozostawia programiście omawia te ograniczenia.
Svelte i Solid stawiają prostsze pytanie: a co, gdy zależność pomiędzy każdym elementem stanu a każdym węzłem DOM byłaby dokładnie znana, tak że przy zmianie stanu modyfikowany byłby tylko ten konkretny węzeł?
Svelte: kompilator, który pisze kod aktualizacji za Ciebie
Svelte to przede wszystkim kompilator. Tworzysz komponenty w plikach .svelte, a podczas budowania Svelte przekształca je w zwykły JavaScript, który bezpośrednio manipuluje DOM-em. Nie ma wirtualnego DOM-u ani mechanizmu porównywania podczas wykonywania, a do przeglądarki trafia tylko niewielki pakiet kodu wykonywalnego.
Od wersji Svelte 5 reaktywność jest wyrażana za pomocą runów – wyraźnych prymitywów rozpoznawanych przez kompilator. Poniższy komponent deklaruje stan oraz wartość pochodzącą z niego, a następnie wyświetla obie w formie przycisku:
<script>
let count = $state(0);
let doubled = $derived(count * 2);
</script>
<button onclick={() => count++}>
{count} doubled is {doubled}
</button>
To jest cała komponent. Nie ma funkcji setter ani tablicy zależności. Zwiększasz count tak, jakby był to zwykła zmienna, a ponieważ kompilator już przeanalizował, które części markupu czytają count i doubled, generuje kod, który aktualizuje dokładnie te węzły tekstowe. $derived jest przeliczany tylko wtedy, gdy zmienia się coś, co czyta. Należy zauważyć, że obsługi wydarzeń w Svelte 5 to zwykłe atrybuty, takie jak onclick, które zastępują starszą składnię dyrektywy on:click.
Dlaczego zespół je lubi
- Mniej kodu dla tego samego efektu. Bez hooków, funkcji setter czy komponentów otaczających, komponenty Svelte zazwyczaj są znacznie krótsze od swoich odpowiedników w React. Mniej kodu oznacza zazwyczaj mniej miejsc na błędy oraz szybszą ocenę.
className, co ułatwia pracę projektantom i recenzentom, którzy nie piszą w React.Dla pełnych aplikacji SvelteKit dodaje routowanie, renderowanie na serwerze oraz punkty końcowe API, pełniąc rolę Next.js dla React, przy jednoczesnej sławie łatwiejszej konfiguracji.
Jedna ważna uwaga, którą warto znać od razu: runy to funkcje kompilatora, więc działają jedynie w plikach .svelte oraz w modułach o nazwach z rozszerzeniami .svelte.js lub .svelte.ts. Przeniesienie logiki reaktywnej do zwykłego pliku narzędziowego .js nie zadziała bez takiej nazwy pliku.
SolidJS: JSX, który uruchamia się raz
Pierwszy rzut oka może sprawić, że pomyli się Solid z Reactem. Używa on JSX, komponuje małe funkcje, a licznik wygląda niemal identycznie:
function Counter() {
const [count, setCount] = createSignal(0);
return (
<button onClick={() => setCount(count() + 1)}>
Count: {count()}
</button>
);
}
Różnica, która zaskakuje deweloperów Reacta, polega na tym, że Counter jest wykonywany dokładnie raz. Solid opiera się na drobnozrębnej reaktywności za pomocą sygnałów. Funkcja createSignal zwraca gettera i settera, przy czym getter count() to wywołanie funkcji, a nie zwykła wartość. Gdy JSX odczytuje count() wewnątrz wyrażenia, Solid rejestruje, że ten konkretny węzeł tekstowy zależy od danego sygnału. Wywołanie setCount później aktualizuje właśnie ten węzeł tekstowy, a nic więcej. Funkcja komponentu była jedynie krokiem konfiguracyjnym, który łączył sygnały z węzłami DOM; nie jest już nigdy wykonywana, więc nie ma nic do ponownego renderowania.
To właśnie dzięki temu modelowi Solid osiąga wyniki na poziomie lub bliskim najwyższych w standardowych testach wydajności frameworków, często porównywalne z wynikami napisanego ręcznie czystego JavaScriptu. Traktuj każdy ranking z testów jako chwilowy obraz sytuacji i mierz własny obciążenie, ale przewaga architektoniczna jest rzeczywista: aktualizacje kosztują mniej więcej proporcjonalnie do tego, co się zmieniło, a nie do wielkości drzewa komponentów.
Dlaczego zespół go lubi
- Brak konieczności ponownego renderowania. Nie pojawia się problem typowy dla Reacta dotyczący tego, dlaczego coś zostało wyrenderowane. Funkcje
useMemo,useCallbackiReact.memonie mają odpowiedników, ponieważ nie ma nic do pominięcia.
Habity, które musisz porzucić
Model jednorazowego użycia ma konsekwencje, które sprawiają trudności początkującym. Rozbieranie właściwości na górze komponentu odczytuje ich wartości tylko raz, co zakłóca reaktywność, dlatego właściwości są zazwyczaj dostępne jako props.name. Wczesne zwracanie wartości oraz operatory ternarne w ciele funkcji są ewaluowane tylko raz, stąd Solid oferuje komponenty sterujące przepływem, takie jak Show i For. Gdy te zasady stają się jasne, są spójne, ale stanowią główne źródło błędów u programistów przychodzących z React.
Jak to się porównuje z React i Angular
Głębsza różnica ma charakter filozoficzny, a nie składniowy.
React przeprojektował swój podstawowy model tak, aby umożliwiał funkcję „ponownego uruchomienia i porównania”, i od lat dodaje narzędzia mające na celu obniżenie kosztów obsługi tego modelu. Nadal jest doskonałym wyborem: jego ekosystem jest niezrównany, zatrudnianie w nim przebiega sprawnie, a kompilator React rzeczywiście zmniejsza konieczność ręcznej memoizacji. Wadą jest to, że pracujesz w modelu opracowanym z uwzględnieniem ograniczeń swojej epoki.
Angular to w pełni funkcjonalna opcja dla dużych przedsiębiorstw, oferująca iniekcję zależności, RxJS oraz ścisłe konwencje dotyczące niemal wszystkiego. Dobrze radzi sobie z bardzo dużymi bazami kodu, ale wymaga sporego nakładu pracy i czasu na naukę. Jego najważniejsze ostatnie zmiany, takie jak mechanizm sygnałów i detekcja zmian bez stref, przesuwają go w kierunku bardziej precyzyjnej reaktywności, jaką popularizował Solid.
To zbieżność jest widoczna we wszystkich branżach. Angular przyjął koncepcję sygnałów, React wprowadził kompilator działający w czasie budowania aplikacji, nowsze frameworki takie jak Qwik opierają się na drobnozrębnej reaktywności, a Vue, którego mechanizmy reaktywne od zawsze były bliskie koncepcji sygnałów, bada własne strategie kompilacji. Byłoby przesadą twierdzić, że Svelte i Solid wynaleźły każdą z tych koncepcji, ale już na wczesnym etapie jasno pokazały, że kompilacja i sygnały mogą stanowić podstawę całego frameworka.
Dlaczego programiści nadal zmierzają w tym kierunku
Trzy niezbyt efektowne powody wyjaśniają większość zainteresowania:
- Mniej informacji o frameworku do zapamiętania. Uwaga skupia się na produkcie, a nie na semantyce aktualizacji frameworka. Poprawne zapamiętywanie funkcji zwrotnej nie jest uważane za istotną czynność.
Czy powinieneś przestawić się?
Dla istniejącego produktu prawie na pewno nie od razu. Gdy już działa znaczna aplikacja oparta na React lub Angular, a zespół dobrze zna tę technologię, przepisanie jej to jeden z najskuteczniejszych sposobów opóźnienia projektu. Ważna jest również wielkość ekosystemu: React oferuje biblioteki na niemal każdą potrzebę, a chociaż ekosystemy Svelte i Solid są rozwijane i dynamiczne, są mniejsze, więc wcześnie sprawdź, czy elementy, od których zależysz – takie jak biblioteki komponentów, narzędzia do form i integracje autoryzacyjne – istnieją i są utrzymywane.
Obliczenia zmieniają się w przypadku nowych projektów. Projekt od zera, widget wymagający wysokiej wydajności lub narzędzie wewnętrzne to środowisko o niskim ryzyku, w którym można to wypróbować:
- wybierz Svelte, jeśli chcesz najłatwiejszą ścieżkę nauki oraz rozwiązanie, które w większości przypadków po prostu działa
Główne wnioski
- Model renderowania i porównywania w React jest przewidywalny, ale praca wykonywana jest proporcjonalnie do drzewa komponentów; memoizacja oraz React Compiler zmniejszają tę pracę bez zmiany modelu.
- Svelte 5 przenosi mechanizm reaktywności do kompilatora za pomocą elementów takich jak
$statei$derived, co umożliwia bezpośrednie aktualizacje DOM i tworzy mały silnik w czasie działania. - Solid uruchamia każdy komponent tylko raz i łączy sygnały bezpośrednio z węzłami DOM, co eliminuje ponowne renderowanie, ale wymaga nowych nawyków w zakresie propów i przepływu sterowania.
- Szerzy ekosystem zmierza w kierunku tych samych rozwiązań: analiza w czasie kompilacji, sygnały zamiast ponownego renderowania oraz bezpośrednie aktualizacje zamiast porównywania zmian.
- Przyjmij te frameworki tam, gdzie ich zalety są istotne i ekosystem spełnia twoje potrzeby; nie przepisuj zdrowej bazy kodu tylko po to, by podążać za trendem.