W maju 2023 sporo się działo. Next.js 13.4 wyraźnie pokazał w jaką stronę zmierza obecnie React, Angular przeszedł totalną rewolucję, a Vue doczekał się od dawna wyczekiwanej wersji 3.4.
Zanim przejdziemy do dzisiejszego podsumowania, należą się Wam dwa słowa wyjaśnienia. W kwietniu Vived przeszedł małe zawirowania (nic strasznego – zespół edytorski nadal działa pełną parą, żeby dostarczać do aplikacji świeże i ciekawe treści), a na to wszystko nałożył się mój prawie miesięczny urlop (jeśli zastanawiacie się czy warto odwiedzić zachodnie wybrzeże USA, to ja jak najbardziej warto!). W efekcie frontendowe podsumowania zniknęły z radarów na prawie 2 miesiące… Teraz wracamy jednak z lekko zmienioną formułą. Zamiast krótkich cotygodniowych podsumowań możecie spodziewać się odrobinę dłuższych comiesięcznych raportów i okazjonalnych wydań specjalnych. Mam nadzieję, że nowy format spodoba się Wam równie mocno jak poprzedni i bez zbędnego przedłużania – łapcie kubek ulubionego napoju, rozsiądźcie się wygodnie i zapraszamy do lektury!
React 19 – Next.js 13.4
Jeśli interesują Was nowości w React, to najlepiej zrobicie jeśli jak najszybciej zaczniecie dokładnie śledzić co dzieje się ekosystemie Next.js. Zespół Reacta skupia swoją uwagę głównie na rozwoju React Server Components(RSC). Ze względu na to, że funkcjonalności tej nie można używać bez frameworka, Meta rozpoczęła ścisłą współpracę z Vercelem (firma matka Next.js) i to właśnie Next.js jest aktualnie pionierem wdrażającym kolejne nowości. Wydany początkiem maja Next.js 13.4 stanowi w tej kwestii spory krok milowy, bo nowy router oparty o React Server Components staje się oficjalnie rekomendowanym rozwiązaniem. Zanim przejdziemy do rzeczy, cofnijmy się o dwa kroki i przypomnijmy czym są React Server Components i jak działa nowy routing w Next.js.
React Server Components
React Server Components zostały po raz pierwszy zaprezentowane przez Dana Abramova w grudniu 2020 roku. W swoich założeniach koncepcja jest prosta – część z komponentów będzie renderowana po stronie klienta, a część po stronie serwera. Dzięki jasnej separacji między serwerem i przeglądarką, serwerowe komponenty otrzymają dostęp do nowych API i będą mogły na przykład bezpośrednio odpytywać bazę danych, czy korzystać z systemu plików. Deweloperzy będą mogli również przestać martwić się o rozmiar zależności w serwerowych komponentach, gdyż te nigdy nie będą wysyłane do klienta.
"use server"
import db from 'db';
//This is interactive client component
import NoteEditor from 'NoteEditor';
async function Note(props) {
const {id, isEditing} = props;
const note = await db.posts.get(id);
return (
<div>
<h2>{note.title}</h2>
<section>{note.body}</section>
{isEditing
? <NoteEditor note={note} />
: null
}
</div>
);
}
Architektura React Server Components różni się mocno od standardowego podejścia do renderowania po stronie serwera. Po pierwsze, w standardowym podejściu, serwer czeka, aż cała strona zostanie wyrenderowana. Oznacza to, że jeśli serwer potrzebuje wykonać powolne zapytanie, to cała strona będzie ładować się powoli. Przy wykorzystaniu React Server Components, szkielet aplikacji szybko trafia do przeglądarki, a kolejne komponenty są stopniowo strumieniowe w postaci pseudo HTMLa. Po drugie, w standardowym podejściu, framework po stronie klienta przejmuje kontrolę nad aplikacją, co skutkuje tym, że wszystkie komponenty być przystosowane do działania zarówno po stronie klienta jak i serwera. W przypadku React Server Components kod komponentów nigdy nie jest wysyłany do klienta i renderowanie zawsze odbywa się po stronie serwera.
React Server Components mają też swoje ograniczenia. Po pierwsze, ze względu na zupełnie inny cykl życia, nie można w nich wykorzystywać hooków useState
i useEffect
. Po drugie, między komponentami renderowanymi po stronie serwera i kliena nie możemy przechodzić całkiem swobodnie. Będąc bardziej precyzyjny, komponenty renderowane po stronie klienta nie mogą bezpośrednio importować komponentów renderowanych po stronie serwera. Możliwe jest jedynie przekazanie takiego komponentu jako jeden z propsów. Pomimo tego, że na pozór może się to wydawać sporym ograniczeniem, to w ostatecznym rozrachunku nie sprawia to wielu problemów.
Od 2020 roku, zespół React intensywnie pracuje nad rozwojem React Server Components. Po drodze zmienił się na przykład sposób oznaczania komponentów czy sposób obsługi asynchronicznych operacji. Po 3 latach intensywnego rozwoju, chyba doczekaliśmy się w końcu w miarę stabilnego API. Jeśli jesteście głodni detali dotyczący serwerowych komponentów, to osobiście polecam oryginalną prezentację Dana Abramova i świetny blog post z Plasmic Blog w detalach pokrywający ten temat.
Next.js Page Router
Wraz z Next.js 13 zaprezentowany został nowy model routingu dla Next.js. Wprowadzał on sporo drobnych, aczkolwiek istotnych usprawnień. Wśród nich znajdziemy między innymi natywne wsparcie dla zagnieżdżonych szablonów czy lepszą obsługę błędów i ładowania. Pośród nowości najbardziej wybrzmiewa jednak fakt, że nowy router zbudowany został w oparciu o React Server Components. Oznacza to, że wszystkie komponenty są komponentami serwerowymi, a przyszłość Next.js ściśle związana jest z przyszłością React Server Components. Jeśli jesteście ciekawi szczegółów na temat tego jak działa nowy Page Router, to poświęciliśmy temu tematowi większość jednej z edycji naszego raportu.
Next.js 13.4, Page Router i React Canary
Jeśli odpowiednio połączyliście kropki, to zapewne teraz drapiecie się po głowie – Jak to jest, że Next.js domyślnie zaleca stosowanie React Server Components, jeśli nie są one jeszcze stabilną funkcjonalnością? Jest to możliwe dzięki nowemu kanałowi releasów na jakie zdecydował się zespół Reacta. Do wersji Canary trafiać będą funkcjonalności o stabilnym API, które wydane zostaną wraz z kolejną większą wersją Reacta. Dlaczego zespół nie zdecydował się po prostu częściej publikować kolejnych wersji biblioteki? Czasami zdarza się, że release zblokowany jest na kilku brakujących funkcjonalnościach kompletnie nie związanych z ustabilizowanym właśnie API. Dzięki nowemu kanałowi releasów, społeczność szybciej będzie mogła testować nowe funkcjonalności, równocześnie nie martwiąc się, że kolejna eksperymentalna wersja całkowicie zepsuje ich aplikacje.
React Server Actions
Jeśli regularnie czytacie nasze przeglądy, to na pewno widzieliście już nie jeden mem na temat tego jak React upodabnia sie do PHP. No cóż, wraz z Next.js 13.4 poczyniony został kolejny krok w tym kierunku. React Server Actions umożliwiają wywołanie funkcji na serwerze i całkowicie ukrywają przed programistą warstwę komunikacji(innymi stare dobre Remote Protocol Call). Wszystko co musimy zrobić, to zacząć naszą funkcję od 'use server;
i voilà – kod będzie wykonywany po stronie serwera i nigdy nie trafi do klienta.
import { cookies } from 'next/headers';
export default function AddToCart({ productId }) {
async function addItem(data) {
'use server';
const cartId = cookies().get('cartId')?.value;
await saveToDb({ cartId, data });
}
return (
<form action={addItem}>
<button type="submit">Add to Cart</button>
</form>
);
}
Co istotne, React Server Actions mają swoje ograniczenia. Po pierwsze, funkcja tego typu musi być asynchroniczna. Po drugie, funkcję taką możemy przekazać tylko do formularza, albo do hooka startTransition
. Co ciekawe, w tej pierwszej opcji formularz będzie działał nawet kiedy klient wyłączy po swojej stronie obsługę JavaScript.
React Server Actions zdecydowanie nie są pierwszym podejściem do RPC w JavaScript. Są natomiast pierwszą implementacją zintegrowaną w frontendowy framework. Jeśli uda im się opuścić wersję alpha, to na pewno staną się ciekawym rozszerzeniem dla React Server Components.
Zainstaluj teraz i czytaj tylko dobre teksty!
Angular 16
Nowe wersje Angulara pojawiają się punktualnie co pół roku niczym japoński Shinkansen i w maju światło dzienne ujrzał Angular 16. Patrząc na kilka ostatnich wersji frameworka, nie sposób nie zauważyć, że jego rozwój znacznie przyśpieszył. Co ważne, zespół stojący za frameworkiem wreszcie zaczął wysłuchiwać problemów społeczności. Z wersji na wersję rozwiązywane są teraz największe bolączki deweloperów – i to nawet takie, które towarzyszyły frameworkowi już od zarania dziejów. Angular 16 kontynuuje ten trend i proponuje rewolucję jakiej Angular jeszcze nie widział.
Signals
Angular został zbudowany na fundamencie stosunkowo niewielkiej biblioteki zone.js
. Jest ona odpowiedzialna za monkey-patching asynchronicznych metod przeglądarki. Za każdym razem kiedy którakolwiek z takich metod jest wywoływana, zone.js
powiadamia o tym Angulara, a ten stara się określić czy stan aplikacji uległ zmianie. Dzięki takiej architekturze, Angular w stosunkowo „magiczny” sposób potrafi odświeżać wartość zmiennych w interfejsie użytkownika.
Jak to zwykle bywa, magiczne rozwiązania mają również swoją ciemną stronę. Aby określić co zmieniło się w interfejsie użytkownika, Angular musi wykonywać ciężką i skomplikowaną operację Change Detection. Jeśli kiedykolwiek musieliście optymalizować Angularowe komponenty albo natkneliście się na błędy związane z odświeżaniem interfejsu, na pewno wiecie do czego zmierzam. Magia Angulara potrafi całkiem szybko doprowadzić do problemów z wydajnością i debugowaniem.
Na przestrzeni lat deweloperzy Angulara nauczyli się lawirować między ograniczeniami zone.js
i ChangeDetection
, ale wciąż czuli, że da się to zrobić lepiej. Tym lepiej mają być zaprezentowane w Angularze 16 Signals, które są pierwszym krokiem w kierunku pozbycia się zone.js
. Nie nastąpi to prawdopodobnie szybko (o ile w ogóle – w końcu aplikacji i bibliotek bazujących na zachowaniu zone.js
jest sporo…). Wydaje się jednak, że pod-drzewa komponentów pozbawione zone.js
są już tuż za rogiem.
Wróćmy jednak do clue dzisiejszego tematu i przyjrzymy się czym tak naprawdę są sygnały. Cytując za dokumentacją Angulara: sygnał jest opakowaniem wokół wartości, które jest w stanie powiadomić zainteresowanych konsumentów, gdy ta wartość ulega zmianie. Dla wszystkich operujących na co dzień w Angularze i RxJS jaśniejsza może być analogia do BehaviourSubject
, gdyż dokładnie tym w lekkim uproszczeniu są sygnały.
Sygnały tworzone są za pomocą factory method signal()
przyjmującej wartość początkową jako parametr. Sygnały mogą być komponowane za pomocą metody computed
(). Możliwe jest również wywoływanie efektów ubocznych, gdy wartość sygnału się zmienia, rejestrując odpowiedni callback w metodzie effect()
. W dowolnym momencie możemy odczytać wartość sygnału wywołując go jak funkcję. Sygnały mogą być aktualizowane za pomocą metod set
(), update
() i mutate
(). W dużym uproszczeniu, to wszystko co musicie wiedzieć o sygnałach. Jeśli jesteście natomiast ciekawi jak działają poszczególne bebechy, to poświęciliśmy temu tematowi całą 132 edycję naszego przeglądu.
@Component({
selector: 'my-app',
standalone: true,
template: `
{{ fullName() }} <button (click)="setName('John')">Click</button>
`,
})
export class App {
firstName = signal('Jane');
lastName = signal('Doe');
fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
constructor() {
effect(() => console.log('Name changed:', this.fullName()));
}
setName(newName: string) {
this.firstName.set(newName);
}
}
Oprócz nowego prymitywu zespół Angulara pracuje też nad szeregiem wbudowanych w framework integracji. Dostaniemy więc narzędzia do wygodnej integracji z sygnałów z strumieniami, API frameworka zwracające sygnały zamiast strumieni i reaktywne inputy oparte o sygnały. Szczególnie te ostatnie wyglądają wyjątkowo ciekawie.
Skoro Signals
zachowują się bardzo podobnie do BehaviurSubject
, to czy Angular na pewno potrzebuje kolejnego prymitywu zintegrowanego z frameworkiem? Po pierwsze Signal
to dużo prostsza abstrakcja niż Observable
. Żeby sprawnie operować RxJS
musimy poznać cały szereg skomplikowanych zagadnień takich jak zimne i gorące strumienie, asynchroniczność w JavaScript czy ponad 100 dostępnych w RxJS operatorów. Po drugie, wszystkie strumienie naturalnie grawitują w kierunku zimnych strumieni (wystarczy na dowolnym BehaviourSubject
zastosować operator map
). Sprawia to, że nigdy nie możemy być pewni, że subskrypcja do strumienia synchronicznie zwróci wartość. W efekcie nasze aplikacje zalane są zbędnym i powtarzalnym kodem. Podsumowując, RxJS sprawdza się świetnie, gdy potrzebujemy modelować złożone zdarzenia dziejące się na przestrzeni czasu, takie jak limitowanie zapytań do backendu czy agregowanie kliknięć myszką. Jeśli chodzi o zarządzanie stanem komponentów i aplikacji, RxJS ma więcej wad niż zalet.
Non-Destructive Hydration
Hydracja to proces podczas którego wyrenderowany na serwerze HTML jest przejmowany przez framework po stronie klienta. Większość frameworków w trakcie tego procesu podłącza listenery do istniejących węzłów DOM. Do wersji 16, Angular zamiast wykorzystywać istniejącą strukturę DOM, bazował w całości na strukturze wygenerowanej po stronie klienta. Jak łatwo się domyślić, od czasu do czasu powodowało to drobne glitche i nie było najbardziej optymalną strategią. Od najnowszej wersji, po ustawieniu odpowiedniej flagi, Angular po stronie klienta będzie w stanie operować na strukturze HTML wyrenderowanej na serwerze.
Vite + ESBuild = Speed
Jeśli nigdy nie narzekaliście na powolne budowanie Angulara to… prawdopodobnie nigdy nie pracowaliście z dużym projektem w Angularze. Już od kilku wersji Angular wprowadza spore usprawnienia jeśli chodzi o czas uruchomienia serwera deweloperskiego. Angular 16 całkowicie migruje serwer deweloperski na Vite + ESBuild. Z jednej strony usprawnienia wydajności cieszą, z drugiej rozjazd między narzędziami wykorzystywanymi na produkcji (gdzie wciąż aplikację buduje webpack) i lokalnie trochę martwi.
Vue 3.3
Na Vue 3.3 czekaliśmy od dawna. Już w naszym grudniowym podsumowaniu roku pisaliśmy, że kolejny Vue ukaże się za kilka tygodni. Po drodze zespół dowodzony przez Evana You napotkał sporo perturbacji i z stycznia zrobił się nagle maj. Nie dziwi wiec, że jednym z celi zespołu na kolejne miesiące jest regularne dowożenie mniejszych, inkrementalnych wydań. Czas pokaże, czy cel uda się spełnić, ale zgodnie z zapowiedziami Vue 3.4 ma ukazać się najpóźniej za 6 miesięcy.
TypeScript, TypeScript, TypeScript
Motywem przewodnim Vue 3.3 jest lepsza integracja z TypeScript. Zacznijmy od wsparcia złożonych typów w funkcjach defineProps
i defineEmits
. Jeśli nigdy nie korzystaliście z Vue, to funkcje te służą do definiowania schematu wejść i wyjść z komponentu. Vue w procesie kompilacji wykorzystywał typ danych przekazanych do generycznych metod, co ograniczało zakres możliwości do prostych typów zdefiniowanych w tym samym pliku co komponent. Wraz z Vue 3.3 ograniczenia te znikają całkowicie i do defineProps
i defineEmits
możemy teraz przekazywać dowolne typy – czy to importowane z innych plików, czy to Union Types, czy to zaawansowane potworki wykorzystujące Utility Types.
<script setup lang="ts">
import type { Props } from './foo'
// imported + intersection type
defineProps<Props & { extraProp?: string }>()
</script>
Kolejną nowością w Vue 3.3 są generyczne komponenty. Próbowałem dogrzebać się do informacji, w jaki sposób deweloperzy Vue radzili sobie z generycznymi komponentami do tej pory. Okazuje się, że albo rezygnowali oni całkowicie ze sprawdzania typów, albo skazani byli na pokraczne i skomplikowane rozwiązania. Na szczęście, od teraz rozsądne rozwiązanie jest już wbudowane w framework.
<script setup lang="ts" generic="T">
defineProps<{
items: T[]
selected: T
}>()
</script>
Inne usprawnienia
Oprócz usprawnionej integracji z TypeScript, Vue 3.3 to też mała paczka drobnych usprawnień. Dostaliśmy na przykład nowe, zdecydowanie schludniejsze API dla metody <code>defineEmits
.
// BEFORE
const emit = defineEmits<{
(e: 'foo', id: number): void
(e: 'bar', name: string, ...rest: any[]): void
}>()
// AFTER
const emit = defineEmits<{
foo: [id: number]
bar: [name: string, ...rest: any[]]
}>()
Poza tym dostaliśmy nową metodę defineSlots
, która będzie służyła do zdefiniowania typów slotów w komponencie. Na ten moment kompilator weryfikuje tylko zgodność schematu komponentu, przez co nie da się na przykład zdefiniować skończonej listy akceptowanych komponentów. Rozwiązanie nie jest też wspierane przez Volar
, czyli Language Server wykorzystywany przez Vue, dzięki któremu w VSCode możemy korzystać z podpowiadania składni i innych podobnych dobrodziejstw. Pomimo drobnych ograniczeń, defineSlots
wydaje się krokiem w dobrym kierunku i na pewno ucieszy wszystkich pracujących na co dzień z Vue.
<script setup lang="ts">
defineSlots<{
default?: (props: { msg: string }) => any
item?: (props: { id: number }) => any
}>()
</script>
Przyszłość Vue
Porównując nowości z Vue do tych z Angulara i React, nie sposób nie odnieść wrażenia, że są one trochę mniej ekscytujące. Vue mierzy się właśnie z migracją swoich użytkowników z wersji 2 na 3. Oznacza to, że zespół nie może skupić się całkowicie na rozwoju nowych funkcjonalności. Poprzednio wydana wersja Vue, to w końcu nie Vue 3.2, a Vue 2.7, które portowało nowości z Vue 3 do Vue 2. Wszystko wskazuje na to, że sytuacja wkrótce ulegnie zmianie. Po pierwsze, Vue 2 dokona swojego żywota wraz z końcem tego roku. Po drugie, na tegorocznej konferencji Vuejs Amsterdam, Evan You uchylił rąbka tajemnicy i zdradził, że zespół pracuje nad nowym wydajnym kompilatorem na wzór SolidJS. Po trzecie, Vue to nie tylko Vue. W ostatnich miesiącach narzędzia zbudowane wokół Vue (np. Vite, Volar) zaczęły szturmem zdobywać popularność również poza matczynym frameworkiem i to właśnie tutaj dzieje się teraz najwięcej.
PS Jeśli na co dzień pracujecie z Vue i jeszcze nie widzieliście prezentacji Evana, to najwyższy czas to nadrobić.
Qwik 1.0
Qwik to framework rozwijany przez prawdziwą frontendową drużynę All-Stars. W zespole pracującym nad frameworkiem znaleźć możemy między innymi Miško Heverego (współtwórca Angulara) czy Adama Bradleya (współtwórcę Ionica) – ludzi wiedzących jak zbudować i sprzedać framework. Dodatkowo za Qwikiem stoi coraz szerzej rozpoznawalna firma Builder.io. W jej portfolio oprócz omawianego dziś frameworka znajdziemy między innymi bibliotekę Partytown (umożliwiającą przerzucenie kodu trackerów takich jak Google Analytics do równoległych workerów) i Mitosis (umożliwiającą pisanie komponentów w specjalnym języku, a następnie kompilowanie ich do dowolnego frameworku).
Qwik wywraca do góry nogami obecny paradygmaty renderowania po stronie serwera, poprzez całkowite pozbycie się procesu hydracji. Hydracja, to proces po stronie klienta, podczas którego wyrenderowana po stronie serwera aplikacja ładuje kod JavaScript, następnie powtarza całą logikę inicjalizacyjną i ostatecznie podłącza odpowiednie listenery do istniejącej struktury DOM. O ile klient szybko widzi docelową teść, o tyle sam proces hydracji może trwać od kilku do kilkunastu sekund. Do tego czasu aplikacja pozostaje całkowicie statyczna.
Alternatywa, którą oferuje Qwik, to wznawiane wykonania (ang. Resumable). Paradygmat ten pozwala zapisać stan aplikacji po stronie serwera w taki sposób, aby po stronie klienta możliwe było natychmiastowe wznowienie wykonywania kodu. Dodatkowo Qwik dzieli aplikację na bardzo małe części i nie pobiera całego kodu aplikacji przy starcie. Zamiast tego stosowane są zaawansowane strategie pre-loadingu, które ładują kod tylko niezbędnych komponentów. W ręce programistów trafiają zaawansowane narzędzia umożliwiające określenie jasnych priorytetów względem pobierania komponentów. Jeśli komponent jest nieistotny, to możliwe jest nawet pobranie jego kodu dopiero kiedy użytkownik wejdzie z nim w interakcje.
Składnia Qwika na pierwszy rzut oka bardzo przypomina składnię Reacta i dlatego większość deweloperów szybko się w niej odjnajdzie. Zaskakujące dla nowicjuszy mogą być wszechobecne $
kończące definicje wielu funkcji. Wyznaczają one fragmenty, na jakie Qwik podzieli aplikację i mają kluczowe znaczenie w przypadku zaawansowanych optymalizacji.
import { component$, useStore } from '@builder.io/qwik';
export default component$(() => {
const store = useStore({ count: 0 });
return (
<main>
<p>Count: {store.count}</p>
<p>
<button onClick$={() => store.count++}>Click</button>
</p>
</main>
);
});
jednej strony bardzo przypomina on Reacta, co czyni go przystępnym i zrozumiałym dla początkujących. Z drugiej strony, model mentalny polegający na dzieleniu kodu na tysiące małych kawałków i doładowywanie ich asynchronicznie może wprowadzać na deweloperów spory narzut. Osobiście nie miałem jeszcze okazji pobawić się tą nową, lśniącą zabawką i wstrzymam się jeszcze z ostatecznymi osądami. Podskórnie mam jednak wrażenie, że architektury takie jak Dynamic Islands czy React Server Components nie starają się rozwiązać wszystkich problemów równocześnie i oferują zdecydowanie prostsze modele mentalne.
Zainstaluj teraz i czytaj tylko dobre teksty!
PS: Urodziny Reacta i PHP 🎉
Jeśli nie mieliście w tym miesiącu okazji do świętowania, to już śpieszę z odsieczą. Otóż w maju 28 urodziny świętował PHP, a jego młodszy kolega w postaci Reacta 10. Idealna okazja, żeby trochę powspominać i wszamać ulubiony tort lub wciągnąć lampkę ulubionego alkoholu. Do zobaczenia za miesiąc!
Źródła:
https://nextjs.org/blog/next-13-4
https://react.dev/blog/2023/05/03/react-canaries
https://blog.angular.io/angular-v16-is-here-4d7a28ec680d
https://blog.vuejs.org/posts/vue-3-3
https://www.builder.io/blog/qwik-v1