Zespół stojący za Reactem zaprezentował nowy hook o nazwie `use`. Ten niepozorny twór może na zawsze może zmienić sposób w jaki pobieramy dane do naszych aplikacji.
1. React z natywnym wsparciem dla Promise
Kolejne RFC (Request For Comments) zwyczajowo już wzbudzają duże zainteresowanie w sieci. Nie inaczej było i tym razem, bo nowo opublikowane RFC wprowadza do biblioteki natywne wsparcie dla Promisów. Dla jednych zaproponowane rozwiązanie jest przekompilowane i niepotrzebne. Inni natomiast gloryfikują RFC do poziomu “ostatniej brakującej funkcjonalności Reacta”. Jak to zwykle bywa prawda jest gdzieś pośrodku, a my dzisiaj bliżej przyjrzymy się zaproponowanym nowościom.
Komponenty renderowane po stronie klienta
Widząc tytuł nowego RFC możecie się zastanawiać o co tak naprawdę chodzi. W końcu React jest w pełni kompatybilny z Promisami i zapewne większość z Was miała przyjemność napisać kod podobny do tego:
async function fetchNote(id) {
const data = await fetchNoteFromBackend(id);
return data.json();
}
function Note({id}) {
const [note, setNote] = useState(null);
useEffect(() => {
fetchNote(id).then(it => setNote(it))
}, [id])
if(note === null) {
return <h2> Loading... </h2>
}
return (
<div>
<h2>{note.title}</h2>
<section>{note.body}</section>
</div>
);
}
Biblioteki takie jak React Query dostarczają odrobinę przyjemniejsze API, ale wciąż manualnie obsłużyć musimy poszczególne możliwe stany Promsie
async function fetchNote(id) {
const data = await fetchNoteFromBackend(id);
return data.json();
}
function Note({id}) {
const query = useQuery(`note-{id}`, fetchNote)
if(query.isLoading) {
return (<h2> Loading... </h2>);
}
return (
<div>
<h2>{query.data..title}</h2>
<section>{query.data.body}</section>
</div>
);
}
Rozwiązanie, które proponuje zespół stojący za Reactem, to wprowadzenie nowego hooka o nazwie `use`. Przy jego pomocy możliwe jest odczytanie wartości fulfilled Promise, a obsługa błędów i ładowania oddelegowana zostanie do najbliższego Suspense. Taka architektura pozwala grupować komponenty w konteksty, które są gotowe do wyrenderowania dopiero kiedy wszystkie komponenty załadują dane.
async function fetchNote(id) {
const data = await fetchNoteFromBackend(id, { useCache: true});
return data.json();
}
function Note({id}) {
const note = use(fetchNote(id));
return (
<div>
<h2>{note.title}</h2>
<section>{note.body}</section>
</div>
);
}
function App() {
return (
{/* Loading will be displayed until all Notes fetch necessary data */}
<Suspense fallback={<h2>Loading…</h2>}>
<Note id=”1” />
<Note id=”2” />
<Note id=”3” />
</Suspense>
);
}
Warto zauważyć, że jeśli referencja do Promise zmieni się między renderami, to ponownie wyświetlony zostanie fallback z komponentu Suspense. Jedynym wyjątkiem od tej reguły, jest przekazanie do `use` fulfilled Promise. W takiej sytuacji React od razu wyświetli wyrenderowany komponent. To oznacza, że aby uniknąć niechcianego “mrugania”, niezbędne jest dodanie obsługi cache. Możemy w tej kwestii albo polegać na zewnętrznych bibliotekach, albo po prostu skorzystać `useMemo`. Na szczęście zespół stojący za RFC obiecuję, że niedługo zaprezentuje również API dedykowane temu łatwiejszej obsługi cache.
async function fetchNote(id) {
const data = await fetchNoteFromBackend(id);
return data.json();
}
function Note({id}) {
const promise = useMemo(() => fetchNote(id), [id]);
const note = use(promise);
return (
<div>
<h2>{note.title}</h2>
<section>{note.body}</section>
</div>
);
}
Jeśli myślicie, że zrobiło się skomplikowanie, to dopiero się rozkręcamy. `use` będzie pierwszym i jedynym hookiem, który może być używany wewnątrz instrukcji if czy pętli for.
function Note({id, shouldIncludeAuthor}) {
const note = use(fetchNote(id));
let byLine = null;
if (shouldIncludeAuthor) {
const author = use(fetchNoteAuthor(note.authorId));
byLine = <h2>{author.displayName}</h2>;
}
return (
<div>
<h2>{note.title}</h2>
{byLine}
<section>{note.body}</section>
</div>
);
}
W przyszłości planowane jest również dodanie interfejsu Usable, który pozwoli wpakować do hooka co nam się żywnie podoba. Autorzy RFC jako przykład podają kontekst. Jeśli ich plany wejdą w życie, to w API znajdziemy zarówno use(Context) jak i useContext(Context). Tego pierwszego będzie można używać wewnątrz instrukcji warunkowych, ale tego drugiego już nie.
// ✅ This will work fine
function Author({display}) {
if (display) {
const authors = use(AuthorsContext);
return <h2>{authors[note.authorId]}</h2>;
} else {
return null
}
// 🛑 This will not work
function Author({display}) {
if (display) {
const authors = useContext(AuthorsContext);
return <h2>{authors[note.authorId]}</h2>;
} else {
return null
}
}
Komponenty renderowane po stronie serwera
Druga zaprezentowana w RFC nowość dotyczy React Server Component. Z racji tego, że póki co są one eksperymentalną częścią biblioteki zatrzymajmy się na chwilę, żeby przypomnieć sobie czym właściwie są.
Koncepcja ta po raz pierwszy zaprezentowana została pod koniec 2020 roku. Server Components umożliwiają wyrenderowanie części drzewa komponentów po stronie klienta i część po stronie serwera. Komponenty wyrenderowane po stronie serwera przesyłane są jako strumień HTML, co pozwala odchudzić paczkę wysłaną do klienta. Taka architektura pozwala też dobrać się do typowo serwerowych funkcji (na przykład do bazy danych, czy systemu plików) bezpośrednio z poziomu kodu komponentu.
import fs from 'react-fs';
import db from 'db.server';
function FileNote({id}) {
const note = JSON.parse(fs.readFile(`${id}.json`));
return <NoteWithMarkdown note={note} />;
}
function DatabaseNote({id}) {
const note = db.notes.get(id);
return <NoteWithMarkdown note={note} />;
}
Jeśli szukacie materiałów, które pozwolą Wam lepiej zrozumieć koncepcję Server Components, to nieustannie rekomenduję oryginalną prezentację z 2020 roku. Wszystkich bardziej zdeterminowanych odsyłam natomiast do długiego i obszernego RFC.
Sporym brakiem Server Components była do tej pory przyjazna obsługa asynchronicznych akcji, tak typowych dla serwerowych operacji. Nowo zaprezentowane RFC adresuje ten problem umożliwiając korzystanie z natywnej składni async/await. Jedynym zastrzeżeniem jest fakt, że komponenty używające async/await nie mogą wykorzystywać hooków.
async function Note({id, isEditing}) {
const note = await db.posts.get(id);
return (
<div>
<h2>{note.title}</h2>
<section>{note.body}</section>
</div>
);
}
Niezależnie od tego, czy zaproponowane zmiany przypadły Wam do gustu, czy nie pamiętajmy, że nie należy popadać w skrajne emocje. Zanim RFC zostanie wdrożone najprawdopodobniej upłynie sporo czasu i raczej możemy spodziewać się kilku zmian w specyfikacji. Odrzucać nie możemy również scenariusza, w którym `use` podzieli los `useEvent` i pomimo początkowego optymizmu nigdy nie ujrzy światła dziennego w opisanej tu dzisiaj postaci.
Źródła
https://github.com/reactjs/rfcs/pull/229
https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md
https://www.youtube.com/watch?v=ytXM05PVcFU
Zainstaluj teraz i czytaj tylko dobre teksty!
2. Lerna wstaje z martwych
Lerna to rozwiązanie do zarządzania monorepo, które przez było monopolistą w kategorii JavaScript. Na przestrzeni kilku ostatnich lat projekt napotkał jednak spore turbulencje. W połowie 2020 roku w repozytorium lerny pojawiło się Issue mówiące, że narzędzie nie jest już aktywnie wspierane. Patrząc na historię commitów ciężko nie zgodzić – większość zmian dotyczy aktualizacji zależności oraz napraw krytycznych podatności. W efekcie długich dyskusji do readme trafił zapis informujący o końcu wsparcia dla projektu.
Przez ostatnie dwa lata na miejscu Lerny wyrastać zaczęły nowe alternatywy. Zarówno yarn jak i npm przygotowały natywne wsparcie dla monorepo w postaci workspaces. Nie zabrakło też narzędzi oferujących obsługę bardziej zaawansowanych funkcjonalności w postaci Nx i TurboRepo. Ten pierwszy zresztą w kwietniu tego roku wyprzedził Lernę pod względem pobrań z npm. Chwilę po tym fakcie zdarzyło się coś zupełnie niespodziewanego – firma stojąca za rozwojem Nx ogłosiła, że zamierza zająć się również rozwojem Lerny.
Od czasu ogłoszenia przejęcia projektu w nasze ręce trafiły już dwie duże wersje narzędzia. Pierwsza, jeszcze w maju tego roku, podbiła kluczowe dependencje i usuwała fałszywe ostrzeżenia rzucane w konsoli. Druga opublikowana w zeszłym tygodniu jest pierwszą wersją od ponad dwóch lat, która wprowadza nowe funkcjonalności. Wśród nich znajdziemy
- Włączenie obsługi cachowania paczek przy pomocy nx domyślnie dla wszystkich modułów
- Dodanie obsługi zdalnego cache na wzór tego znanego z nx
- Możliwość definiowania zależności między modułami, przy pomocy konfiguracji umieszczonej w pliku `nx.json`
- Wsparcie dla pnpm
- Dynamiczne wyjście terminala na wzór tego z nx
- Dedykowane VSCode extension, mocno przypominające to znane z nx
Jak widzicie, sporo z nowych funkcjonalności tworzona jest na wzór nx lub wręcz dodaje integrację z nx. Nie ma w tym nic złego, ale ja osobiście trochę żałuję, że Lerna nie rozwija nowych funkcjonalności równolegle do nx. W końcu większa różnorodność od zawsze napędzają innowacje. Na szczęście na rynku mamy jeszcze TurboRepo, więc stagnacji w obszarze monorepo na razie nie musimy się obawiać.
Źródła:
https://blog.nrwl.io/lerna-reborn-whats-new-in-v6-10aec6e9091c