Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds
Powrót do kolekcji
Przewodnik11 min czytania

SolidJS, sygnały i komponent uruchamiany raz

SolidJS wygląda jak React i działa inaczej: komponent uruchamia się raz, a aktualizuje sygnał. Wersja 2.0 w becie, SolidStart i nawyki, które przeszkadzają.

SolidJS, sygnały i komponent uruchamiany raz

SolidJS to biblioteka do budowania interfejsów, która używa tej samej składni co React i działa na zupełnie innej zasadzie. Bieżąca stabilna wersja to 1.9, licencja MIT, a wersja druga jest w fazie beta od marca 2026 roku.

Podobieństwo składni jest tu największą zaletą marketingową i największą pułapką w nauce. Kod wygląda znajomo, więc osoba przychodząca z Reacta pisze go od pierwszego dnia, przenosząc przy okazji nawyki, które w tym modelu nie mają sensu.

Komponent uruchamia się raz

To jest zdanie, które trzeba zrozumieć, zanim cokolwiek innego zacznie mieć sens.

W Reakcie funkcja komponentu wykonuje się przy każdej zmianie stanu, a biblioteka porównuje wynik z poprzednim i aktualizuje to, co się różni. Tutaj funkcja komponentu wykonuje się raz, przy tworzeniu, i buduje reaktywne powiązania między sygnałami a konkretnymi miejscami w dokumencie.

Code
TypeScript
import { createSignal, createEffect } from 'solid-js'

function Licznik() {
  const [liczba, ustawLiczbe] = createSignal(0)

  console.log('to wypisze sie jeden raz')

  createEffect(() => {
    console.log('to wypisze sie przy kazdej zmianie', liczba())
  })

  return <button onClick={() => ustawLiczbe(liczba() + 1)}>{liczba()}</button>
}

Wypisanie poza efektem pojawi się jeden raz, niezależnie od liczby kliknięć. Zmiana sygnału aktualizuje wyłącznie ten fragment dokumentu, który go czyta, bez ponownego wykonywania funkcji.

Konsekwencje są dalekosiężne. Nie ma listy zależności, bo powiązania powstają automatycznie w momencie odczytu. Nie ma potrzeby zapamiętywania funkcji ani wartości, bo nic się nie tworzy ponownie. Nie ma reguł mówiących, gdzie wolno wywołać punkt zaczepienia, bo nie ma kolejności wywołań do zachowania.

Nawyki z Reacta, które przeszkadzają

Warto wymienić je konkretnie, bo to one powodują większość problemów przy pierwszym projekcie.

Odczyt sygnału to wywołanie funkcji, a nie odczyt zmiennej. Przekazanie samej wartości zamiast wywołania zrywa powiązanie i fragment przestaje się aktualizować, bez żadnego błędu.

Code
TypeScript
const [liczba, ustawLiczbe] = createSignal(0)

<Etykieta wartosc={liczba()} />

<Etykieta wartosc={liczba} />

Pierwszy zapis odczytuje wartość raz, w chwili tworzenia komponentu, i zostaje przy zerze na zawsze. Drugi przekazuje samą funkcję, więc odbiorca odczyta ją w swoim miejscu i powiązanie przetrwa. To jest najczęstsza pomyłka i najtrudniejsza do zauważenia, bo wszystko wygląda poprawnie i nic nie zgłasza błędu.

Rozłożenie właściwości na osobne zmienne również zrywa reaktywność. Wartość odczytana raz przy tworzeniu komponentu zostaje taka na zawsze, a właściwości trzeba czytać przez obiekt w momencie użycia.

Code
TypeScript
function Karta({ tytul, opis }) {
  return <h2>{tytul}</h2>
}

function Karta(props) {
  return <h2>{props.tytul}</h2>
}

Jeśli mimo wszystko potrzebujesz rozdzielić właściwości, na przykład żeby część przekazać dalej, biblioteka daje do tego osobne narzędzie zachowujące reaktywność.

Code
TypeScript
import { splitProps } from 'solid-js'

function Przycisk(props) {
  const [wlasne, reszta] = splitProps(props, ['wariant'])
  return <button class={wlasne.wariant} {...reszta} />
}

Warunkowe renderowanie przez wcześniejszy powrót z funkcji nie działa tak, jak się wydaje. Skoro funkcja wykonuje się raz, warunek zostanie sprawdzony raz. Do renderowania warunkowego służą wbudowane komponenty przyjmujące warunek jako właściwość.

Podobnie z listami. Renderowanie tablicy przez zwykłe mapowanie działa i traci przewagę tego modelu, bo przy każdej zmianie odtwarza wszystkie elementy.

Code
TypeScript
import { Show, For, Index } from 'solid-js'

<Show when={uzytkownik()} fallback={<Logowanie />}>
  {(u) => <Profil uzytkownik={u()} />}
</Show>

<For each={zadania()}>
  {(zadanie) => <Wiersz zadanie={zadanie} />}
</For>

<Index each={liczby()}>
  {(liczba, i) => <Pole wartosc={liczba()} indeks={i} />}
</Index>

Różnica między dwoma ostatnimi jest warta zapamiętania, bo wybór złego kosztuje wydajność. Pierwszy porównuje elementy po referencji i nadaje się do list, w których wpisy się dodaje, usuwa i przestawia. Drugi porównuje po pozycji i nadaje się do list o stałej długości, w których zmieniają się wartości, na przykład do pól formularza.

Praktyczna rada: przy pierwszym projekcie warto przeczytać sekcję o pułapkach reaktywności zanim napisze się pierwszy komponent. Te cztery rzeczy odpowiadają za większość sytuacji, w których nowa osoba stwierdza, że coś jest zepsute.

Wersja druga i pierwszorzędna obsługa asynchroniczności

Wersja druga jest w fazie beta od marca 2026 roku, a jej głównym tematem jest to, czego dotąd brakowało: rozumienie obietnic przez graf reaktywny.

Dotychczas dane pobierane asynchronicznie wymagały osobnych mechanizmów, a stan ładowania obsługiwało się ręcznie. W nowej wersji obietnica jest wartością, którą graf reaktywny rozumie, więc oczekiwanie na dane przestaje być czymś obok reaktywności, a staje się jej częścią.

Do tego dochodzą przerobiony mechanizm zawieszania renderowania na czas ładowania oraz przewidywalne grupowanie zmian, czyli rzeczy, które w praktyce decydują o tym, ile migotania widzi użytkownik.

Rzecz najważniejsza dla podejmującego decyzję dziś: to jest beta. Materiały opisujące nowe podejście do asynchroniczności dotyczą czegoś, czego nie postawisz na produkcji bez świadomej zgody na ryzyko. Stabilna linia to nadal 1.9.

Warto też odnotować, że framework budowany na tej bibliotece, czyli SolidStart, poszedł własnym torem. Jego wersja druga wyszła jako stabilna 4 sierpnia 2026 roku i zamieniła dotychczasową warstwę pośrednią na czyste Vite. Co istotne, opiera się na stabilnej linii 1.9 samej biblioteki, a nie na becie wersji drugiej, więc jedno wydanie nie czeka na drugie.

Wydajność i co z niej wynika

W zestawieniach ta biblioteka konsekwentnie plasuje się w czołówce, blisko rozwiązań bez frameworka. Warto jednak wiedzieć, kiedy ta przewaga jest odczuwalna, a kiedy jest liczbą w tabeli.

Odczuwalna jest przy częstych, drobnych aktualizacjach. Tabela z tysiącem wierszy, w której zmienia się jedna komórka co sekundę, pracuje tu inaczej niż przy porównywaniu drzewa. Podobnie widok odświeżany danymi płynącymi ze strumienia.

Odczuwalna jest też przy rozmiarze paczki. Brak warstwy porównującej drzewo znaczy mniej kodu wysyłanego do przeglądarki, co przy stronach ocenianych po czasie ładowania ma znaczenie mierzalne.

Nieodczuwalna jest przy typowej aplikacji formularzowej. Panel z listami i formularzami, gdzie zmiana następuje po kliknięciu użytkownika, działa tak samo szybko w każdym z popularnych rozwiązań, bo wąskim gardłem jest zapytanie do serwera, a nie renderowanie.

Praktyczny wniosek: wydajność jest dobrym argumentem, gdy Twoja aplikacja faktycznie robi coś, co ją obciąża. Jako argument główny przy zwykłym panelu jest rozstrzyganiem sporu, którego nie ma.

Magazyny i stan zagnieżdżony

Sygnał trzyma pojedynczą wartość i to wystarcza do liczby, łańcucha znaków albo flagi. Przy obiektach i tablicach właściwym narzędziem jest magazyn.

Różnica polega na ziarnistości. Sygnał trzymający obiekt reaguje na podmianę całego obiektu, więc zmiana jednego pola powiadamia wszystkich, którzy czytają cokolwiek z tego obiektu. Magazyn śledzi pola osobno, więc zmiana nazwy użytkownika nie odświeża fragmentu wyświetlającego jego adres.

Zapis do magazynu wygląda jak zwykłe przypisanie, choć pod spodem działa mechanizm śledzący. To jest wygodne i ma jedną pułapkę: struktura zagnieżdżona wymaga wskazania ścieżki, a nie podmiany całości, bo podmiana traci dokładnie tę ziarnistość, dla której magazyn został użyty.

Praktyczna reguła: sygnał do wartości prostych, magazyn do struktur. Sygnał trzymający tablicę stu obiektów jest sygnałem błędu w projekcie, bo każda zmiana jednego elementu unieważnia wszystko, co czyta tę tablicę.

Warto też pamiętać, że magazyny są tu wbudowane i nie wymagają osobnej biblioteki do zarządzania stanem. Stan wspólny dla aplikacji tworzy się jako magazyn poza komponentem i importuje tam, gdzie potrzebny, bez dostawcy owijającego drzewo. To jest jedna z rzeczy, które upraszczają projekt bardziej, niż wynikałoby z opisu, bo znika cała kategoria decyzji o tym, gdzie umieścić dostawcę i jak podzielić konteksty.

Renderowanie serwerowe i SolidStart

Sama biblioteka obsługuje renderowanie po stronie serwera, a framework wokół niej dokłada routing, ładowanie danych i budowanie.

Model ładowania danych przypomina ten znany z frameworków opartych o trasy: dane pobiera się na poziomie trasy, przed renderowaniem, więc nie ma kaskady zapytań uruchamianych po zamontowaniu komponentów.

Funkcje serwerowe pozwalają wywołać kod działający po stronie serwera z komponentu, bez pisania osobnej trasy interfejsu. To jest wygodne i ma tę samą pułapkę co wszędzie: funkcja wygląda na lokalną, a jest wywołaniem sieciowym, więc walidacja i uprawnienia muszą być w niej, nie w komponencie, który ją woła.

Stan projektu wart odnotowania przy planowaniu: wersja druga frameworka jest już stabilna, wydana 4 sierpnia 2026 roku, i buduje na czystym Vite zamiast na dotychczasowej warstwie pośredniej. Starsze materiały opisują ją jeszcze jako alfę albo betę, więc przy poradniku znalezionym w sieci warto spojrzeć na datę. Sam framework pracuje na bibliotece w linii 1.9, więc jego stabilność nie zależy od bety wersji drugiej.

Warto też sprawdzić dostępność bibliotek pomocniczych przed decyzją. Routing, formularze i praca z danymi mają tu rozwiązania, natomiast przy bardziej specyficznych potrzebach lista opcji jest krótka, a napisanie czegoś samodzielnie wchodzi w rachunek częściej niż w większych ekosystemach.

SolidJS kontra alternatywy

RozwiązanieModelEkosystemKiedy wybrać
SolidJSSygnały, komponent razMałyCzęste drobne aktualizacje, mała paczka
ReactPonowne renderowanieNajwiększyRynek pracy, dostępność bibliotek
AngularSygnały, framework z wyposażeniemDużyDuży zespół, narzucona struktura
VueReaktywność, framework pośredniDużyŁagodne wejście, mniejszy zespół

Rzecz warta powiedzenia wprost: pod względem modelu reaktywności różnica wobec pozostałych zmalała. Sygnały trafiły do Angulara i do Vue, a i w Reakcie temat wraca, więc argument o wyjątkowości tego podejścia jest dziś słabszy niż dwa lata temu.

Najbliżej tego modelu stoi Qwik, który również opiera stan na sygnałach, tylko podporządkowuje je wznawianiu aplikacji zamiast szybkości aktualizacji. Jego druga wersja pozostaje w becie, a ekosystem jest tam jeszcze węższy niż tutaj, więc liczy się jako alternatywa przy stronach treściowych, a nie przy pulpicie z danymi na żywo.

To, co zostaje, to konsekwencja. Tutaj sygnały nie są dodatkiem obok istniejącego modelu, tylko całym modelem, więc nie ma dwóch sposobów robienia tej samej rzeczy ani warstwy zgodności ze starszym podejściem.

Czynnik rozstrzygający przy projekcie komercyjnym jest jednak zwykle inny i warto go nazwać uczciwie. Ekosystem jest mały. Biblioteki komponentów, integracje i gotowe rozwiązania istnieją w liczbie nieporównywalnej z pierwszym wierszem, a osób znających to narzędzie jest mało. Przy zespole, który ma to utrzymywać przez lata, ten czynnik przeważa nad różnicą w wydajności.

Kiedy warto po to sięgnąć

Trzy przypadki, w których wybór broni się także po chłodnej kalkulacji.

Pierwszy to widżet osadzany na cudzych stronach. Mała paczka i brak zależności od ciężkiego środowiska uruchomieniowego mają tu bezpośrednie przełożenie na czas ładowania strony, na której siedzisz jako gość.

Drugi to interfejs z dużą liczbą drobnych aktualizacji: pulpit z danymi na żywo, edytor, wizualizacja reagująca na strumień zdarzeń. Tam model reaktywności przekłada się na płynność, którą widać.

Trzeci to projekt osobisty albo zespół, który świadomie chce pracować w tym modelu. To jest uzasadnienie równie dobre jak każde inne, o ile decyzja jest podjęta z pełną świadomością wielkości ekosystemu i tego, kto będzie to utrzymywał za dwa lata.

Poza tymi trzema przypadkami trudno uzasadnić wybór przy projekcie komercyjnym z długim horyzontem, i warto to powiedzieć wprost zamiast udawać, że wydajność wygrywa wszystko.

Typowe błędy

Pierwszy to przekazywanie wartości sygnału zamiast jego wywołania. Powiązanie zostaje zerwane, fragment przestaje się aktualizować i nie ma żadnego błędu.

Drugi to rozkładanie właściwości na osobne zmienne. Wartość odczytana raz przy tworzeniu komponentu zostaje taka na zawsze.

Trzeci to renderowanie warunkowe przez wcześniejszy powrót z funkcji. Funkcja wykonuje się raz, więc warunek zostanie sprawdzony raz.

Czwarty to renderowanie list przez zwykłe mapowanie. Działa i odtwarza wszystkie elementy przy każdej zmianie, czyli traci główną przewagę tego modelu.

Piąty to oczekiwanie na punkty zaczepienia znane z Reacta. Nie ma tu list zależności ani zapamiętywania, bo nie ma czego zapamiętywać, i szukanie odpowiedników prowadzi w złą stronę.

Szósty to budowanie na wersji drugiej przy projekcie produkcyjnym. To jest beta, a stabilną linią jest nadal 1.9.

Siódmy to trzymanie struktur w sygnale zamiast w magazynie. Zmiana jednego pola unieważnia wszystko, co czyta ten obiekt, więc przewaga drobnoziarnistej reaktywności znika dokładnie tam, gdzie miała działać.

Ósmy to walidacja wywołania serwerowego po stronie komponentu. Funkcja wygląda na lokalną, a jest wywołaniem sieciowym, które da się wykonać z pominięciem interfejsu, więc sprawdzanie danych i uprawnień musi być w niej samej.

FAQ

Czym SolidJS różni się od Reacta?

Modelem aktualizacji. Tutaj funkcja komponentu wykonuje się raz i buduje powiązania między sygnałami a miejscami w dokumencie, więc zmiana sygnału aktualizuje wyłącznie te miejsca. W Reakcie funkcja wykonuje się przy każdej zmianie, a biblioteka porównuje wynik z poprzednim.

Czy wersja 2.0 jest gotowa?

Nie, jest w fazie beta od marca 2026 roku, a stabilną linią pozostaje 1.9. Materiały opisujące pierwszorzędną obsługę asynchroniczności dotyczą właśnie tej bety, więc przed wdrożeniem produkcyjnym warto sprawdzić bieżący stan.

Dlaczego mój fragment przestał się aktualizować?

Najczęściej dlatego, że gdzieś odczytano wartość sygnału zamiast przekazać jego wywołanie, albo rozłożono właściwości na osobne zmienne. Oba zrywają powiązanie reaktywne i nie zgłaszają błędu.

Czy warto wybrać to do projektu komercyjnego?

Zależy od horyzontu i zespołu. Model jest spójny, a wydajność realna, natomiast ekosystem jest mały i osób znających to narzędzie jest niewiele. Przy projekcie utrzymywanym latami ten czynnik zwykle przeważa nad różnicą w wydajności.

Czym jest SolidStart?

Frameworkiem zbudowanym na tej bibliotece, dodającym routing, renderowanie serwerowe i budowanie. Wersja druga wyszła jako stabilna 4 sierpnia 2026 roku i buduje na czystym Vite, a pracuje na bibliotece w linii 1.9, więc nie ciągnie za sobą bety wersji drugiej.

Dokumentacja stoi na stronie projektu, a wydania w repozytorium na GitHubie.