Wybór struktury folderów w React: siedem układów i ich punkty krytyczne
Porównaj układy flat, typowe, oparte na cechach, Atomic Design, DDD, Feature-Sliced Design oraz monorepo dla React i dowiedz się, na jakim poziomie skali każdy z nich przestaje być efektywny.
Źródła: React Folder Structure: 7 Ways to Organize a React App (And Exactly When Each One Breaks)
Każda rozwijająca się baza kodu w React w końcu napotyka to samo pytanie: gdzie powinien trafić ten plik? Nie ma oficjalnej odpowiedzi, a mimo to zespoły spędzają całe sprints na migracji między różnymi strukturami, a rekruterzy nadal pytają o to kandydatów. Ten przewodnik porównuje siedem powszechnych sposobów organizacji aplikacji React – od prostego katalogu src/ po monorepo z wieloma aplikacjami – korzystając przy tym z jednej aplikacji referencyjnej. Dla każdej struktury pokażemy, czym jest, kto ją faktycznie używa, w czym sprawdza się najlepiej, gdzie występują problemy oraz dokładny moment, w którym przestaje funkcjonować, abyś mógł wybrać najprostszą strukturę pasującą do Twojej rzeczywistej aplikacji.
Nikt oficjalny nie wybierze tego za Ciebie
Dokumentacja z okresu Legacy React zawierała kiedyś stronę poświęconą strukturze plików. Zaczynała się od stwierdzenia, że React nie ma żadnej opinii na temat tego, jak pliki powinny być umieszczane w folderach, i radziła osobom rozpoczynającym projekt, aby nie poświęcały więcej niż pięć minut na wybór układu. Strona ta została później usunięta, gdy dokumentacja przeniosła się na react.dev, a nic jej nie zastąpiło. Dziś React w praktyce w ogóle nic nie mówi o folderach. Stare wytyczne przetrwały na archiwizowanej stronie FAQ.
Frameworki i narzędzia współpracujące z Reactem są równie niejednoznaczne. Next.js określa siebie jako nieposiadający żadnych preferencji co do sposobu organizacji i umieszczania plików projektu. Szablon Reacta w Vite generuje zaledwie kilka plików źródłowych, bez żadnych folderów. Żaden autorytet w tym ekosystemie nie podaje wytycznych co do tego, jak postąpić.
To brak wyjaśnia wiele rzeczy. Struktury takie jak Bulletproof React zgromadziły dziesiątki tysięcy gwiazdek na GitHubie głównie za proponowanie określonego układu folderów, a programiści nieustannie spierają się na ten temat. Szczere sformułowanie wszystkiego, co następuje, brzmi tak: te siedem struktur to konwencje, a nie standardy. Żadna z nich nie jest popierana przez sam React. Ważne jest zrozumienie kosztów każdej z nich oraz, przede wszystkim, sytuacji, w której zawodzi, ponieważ to właśnie ta kwestia staje się decydująca, gdy aplikacja staje się rzeczywistością.
Dwa zasady leżące u podstaw każdego układu
Twórcy są bardziej spokojni niż ewangelizatorzy
Ludzie, którzy stworzyli te metodologie, zazwyczaj traktują je znacznie swobodniej niż ci, którzy je promują. Brad Frost, twórca Atomic Design, opublikował w 2019 roku książkę „Extending Atomic Design”. W niej opisuje, jak ludzie kontaktują się z nim, aby „skorygować” jego terminologię, podkreśla, że atomic design nie jest sztywnym dogmatem, i mówi, że zespoły chcące użyć dziewięciu etapów nazwanych na cześć członków zespołu Chicago mogą to robić. Można ją przeczytać na Extending Atomic Design. Twórca tej metody mówi, że nazwy nie mają znaczenia, podczas gdy gdzieś praca nad przeglądem kodu utknęła z powodu kwestii, czy dany komponent można uznać za cząsteczkę.
Stare dokumentacje Reacta również wspominały o Atomic Designie bezpośrednio. Zaznaczono w nich, że niektórzy programiści dzielą komponenty na foldery według ich roli w aplikacji, podając Atomic Design jako przykład takiego podejścia, oraz zalecano traktowanie takich metodologii jako przydatnych przykładów, a nie ścisłych zasad. Pochodząc z samej dokumentacji Reacta, jest to najlepiej udokumentowana krytyka ścisłego stosowania Atomic Designu w Reactie, jaka istnieje.
Screaming Architecture
W tej debacie istnieje naprawdę tylko jeden ważny argument, który jest starszy od React. We wrześniu 2011 roku Robert C. Martin opublikował Screaming Architecture. Jego pytanie brzmiało: gdy ktoś spojrzy na najwyższe poziomy katalogów twojej aplikacji, czy od razu dostrzeże system opieki zdrowotnej, system rachunkowości czy system zarządzania zapasami? Jego wniosek był taki, że architektura powinna informować czytelników o systemie, a nie o frameworkach, które się w nim znajdują.
To podejście leży u podstaw każdego układu opartego na komponentach. Katalog src/ zawierający foldery components/, hooks/, utils/ oraz services/ wskazuje, że mamy do czynienia z aplikacją React. Katalog src/ zawierający foldery checkout/, billing/ i inventory/ informuje o działalności firmy. Każda struktura opisana w tym przewodniku jest albo wariacją tego podejścia, albo jego świadomym odrzuceniem.
Kolokacja
Druga zasada ma największe znaczenie w praktyce codziennej. Kent C. Dodds nazywa ją kolokacją – należy trzymać kod jak najbliżej miejsca, w którym jest on istotny. Przypisuje Danowi Abramovowi bardziej precyzyjne sformułowanie, zgodnie z którym elementy, które zmieniają się razem, powinny znajdować się jak najbliżej siebie w ramach rozsądku. Pełny wywód znajduje się w artykule Colocation.
Z tego punktu widzenia każda struktura w tym przewodniku odpowiada na jedno pytanie: które elementy twojego kodu rozwijają się jako całość? Jeśli udasz na nie poprawną odpowiedź, foldery niemal same się ukształtują. Jeśli odpowiesz błędnie, żadna metodyka ci nie pomoże.
Uwaga dotycząca popularnego cytatu: rada, by „przesuwać pliki, aż będzie to wydawało się właściwe”, jest powszechnie przypisywana Danowi Abramovowi i czasami cytowana jako oficjalna dokumentacja React. Nie jest to dokumentacja; w niej znajduje się zamiast tego rada trwająca pięć minut, a wypowiedź Abramova pochodzi z tweeta, który najprawdopodobniej został usunięty. Idea jest sensowna; traktuj tę wzmiankę jako folklor.
Jedna aplikacja referencyjna, siedem wariantów układu
Porównywanie układów jest sensowne tylko wtedy, gdy aplikacja pozostaje niezmienna; w przeciwnym razie porównuje się przykłady zamiast struktur. Dlatego każdy z poniższych układów organizuje tę samą małą aplikację e-commerce z tymi samymi plikami:
- Funkcje: autoryzacja (logowanie i rejestracja), katalog (lista produktów i ich szczegóły), koszyk, proces płatności oraz zamówienia.
- Elementy interfejsu:
Button,Input,Modal,BadgeorazSpinner. - Hooki:
useAuth,useCart,useProducts,useCheckoutorazuseDebounce. - Modyuły API:
authApi,productsApi,checkoutApi,ordersApioraz wspólnyapiClient. - Inne: pomocnik
cartStoreorazformatPrice.
Cztery z tych plików są celowo skomplikowane, ponieważ pokazują, w których miejscach każda struktura zaczyna sprawiać problemy:
Modal.jsxotrzymuje etykietę „atom”, „molekuła” lub „organizm” w zależności od osoby, która go klasyfikuje, co podkreśla taksonomię Atomic Design.useDebounce.jsjest całkowicie uniwersalny, a mimo to używa go tylko wyszukiwanie w katalogu. Czy jest to globalny hook, czy hook katalogowy?CartTotal.jsxjest renderowany zarówno w panelu koszyka, jak i na ekranie płatności, więc nie jest jasne, kto nim zarządza.formatPrice.jswygląda na prosty narzędzie, chyba że pieniądze stanowią rzeczywisty pojęcie z danej dziedziny – w takim przypadku powinien to być obiekt wartościMoney, a umieszczenie go w folderze z narzędziami jest błędem.
Pamiętaj o tych czterech kwestiach. Każda struktura radzi sobie z nimi inaczej, a właśnie te różnice odróżniają jeden układ od drugiego.
Struktura płaska
Czym jest
Wszystko znajduje się bezpośrednio w katalogu src/, bez folderów lub prawie bez nich, ułożone alfabetycznie, ponieważ nic innego nie narzuca żadnego porządku.
Nikt nie opublikował tego jako metodyki. To po prostu punkt wyjścia dla każdego projektu, w którym pozostaje zaskakująco dużo z nich. Dokumentacja dziedzictwa Reacta nawet polecała to jako punkt startowy: jeśli czujesz się zagubiony, przechowuj wszystkie pliki w jednym folderze, a gdy stanie się on na tyle duży, że będzie trzeba go podzielić, dowiesz się, które pliki zwykle edytujesz razem. To naprawdę dobra rada. Struktura wywodząca się z zaobserwowanych wzorców zmian jest lepsza od tej narzuconej od samego początku.
Kto ją używa
Prototypy, wersje testowe, dema w środowisku sandbox, projekty z hackathonów, zadania do wykonania w domu oraz narzędzia wewnętrzne z jednym administratorem. Ponadto opisuje wiele aplikacji produkcyjnych, które nigdy nie zostały przerobione.
Oficjalny szablon Vite React jest zasadniczo prosty. Uruchomienie polecenia npm create vite@latest -- --template react-ts tworzy pliki main.tsx, App.tsx, App.css, index.css oraz folder assets/, bez folderów components/ czy features/. Ponieważ szablon nie wyraża żadnej opinii, dyskusje przenoszą się do poszczególnych zespołów, które go używają.
Zalety
- Nawigacja nie kosztuje nic. Istnieje tylko jeden folder, a wybieracz plików znajduje wszystko po nazwie. Ponieważ nie ma decyzji dotyczących rozmieszczenia, nie ma też sporów na ten temat.
Słabości
- Skalowanie ustaje nagle po około 30 plikach. Problem nie występuje stopniowo: pewnego dnia lista plików już nie mieści się w pasku bocznym, a kształt aplikacji znika.
- Kolejność alfabetyczna nie ma żadnego znaczenia.
authApi.jsznajduje się obokApp.jsxwyłącznie dlatego, że oba zaczynają się na literę A, podczas gdyuseAuth.jsiLoginForm.jsx, które zawsze edytuje się razem, znajdują się daleko od siebie. Kolejność sortowania stoi w sprzeczności z logiką umieszczania elementów obok siebie. - Nie ma żadnych granic do egzekwowania. Każdy plik może importować każdy inny, zależności cyrkularne pojawiają się niezauważenie, a zanim chce się wprowadzić granice, każde naruszenie ma już znaczenie.
- Usunięcie określonej funkcji staje się zadaniem detektywistycznym. Usunięcie procesu płatności oznacza konieczność przeczytania plików, aby ustalić, które do niego należą, ponieważ nie ma folderu do usunięcia.
Gdzie to zawodzi
Mniej więcej przy 30 plikach, albo w momencie dołączenia drugiego programisty – ten drugi próg jest ważniejszy. Ustrój płaski działa, ponieważ jedna osoba ma mapę w głowie. Przy dwóch osobach istnieją dwie mapy, które natychmiast się różnią.
Struktura oparta na typach
Czym jest
Pliki są grupowane według ich rodzaju: komponenty razem, hooki razem, wywołania API razem, magazyny danych razem. Dokumentacja z czasów Reacta opisywała to jako grupowanie według typu pliku i przedstawiała je jako jeden z dwóch powszechnych podejść, nie faworyzując żadnego z nich. W przypadku aplikacji referencyjnej oznacza to Button, Modal, CartItem, CheckoutForm oraz wszystkie pozostałe komponenty w katalogu components/, wszystkie hooki w hooks/, wszystkie moduły API w api/ oraz cartStore w katalogu store/.
Ponieważ większość tutoriali tego uczy, jest to również struktura, którą najczęściej spotkasz w rzeczywistych projektach.
Kto jej używa
Prawie każdy tutorial dotyczący React, większość projektów z bootcampów oraz znaczna część aplikacji produkcyjnych, które przerosły prosty układ bez żadnego planu, wykorzystuje tę strukturę. To najprostszy krok do podjęcia: gdy folder src/ staje się zagracony, naturalną pierwszą reakcją jest utworzenie folderu components/.
Zalety
- Nie wymaga żadnych wyjaśnień. Nikt nigdy nie musiał być pouczany, co powinno znajdować się w folderze
hooks/, więc nowi programiści mogą od razu przyczynić się do pracy, używając słownictwa, które już znają. - Klasyfikacja jest prosta. Komponent trafia do folderu
components/, hook do folderuhooks/, a sam plik wskazuje, o co chodzi. Porównaj to z podejściem Atomic Design, gdzie nawet rozsądni ludzie mogą godzinami dyskutować o jednym komponencie. - Jest doskonale odpowiednia na małą skalę. Przy mniej niż około 50 plikach działa dobrze, a przechodzenie na inne rozwiązanie przyniosłoby więcej kosztów niż korzyści.
Słabości
- Zamiast produktu prezentuje się tu framework, co jest dokładnie tym, czemu sprzeciwia się koncepcja Screaming Architecture. Katalogi najwyższego poziomu o nazwach
components/,hooks/,api/iutils/informują początkującego, że używa się React, ale nic nie mówią o tym, czym zajmuje się aplikacja. Sklep internetowy i system planowania w szpitalu wyglądają identycznie z poziomu katalogu głównego.
CartItem.jsx znajdującego się w katalogu components/, pliku useCart.js w katalogu hooks/, pliku cartApi.js w katalogu api/ oraz pliku cartStore.js w katalogu store/. To cztery katalogi i cztery pozycje przewijania dla jednej logicznej zmiany. Zasada rozmieszczenia plików jest złamana – pliki, które najprawdopodobniej ulegną zmianie w ramach jednego commitu, są umieszczane najdalej od siebie.components/ zamienia się w nową, płaską strukturę katalogów. W aplikacji referencyjnej zawiera on około 18 plików bez żadnego wewnętrznego podziału. Problem płaskiej struktury zostaje rozwiązany w przypadku hooków i modułów API, by następnie ponownie pojawić się w katalogu, który zawiera większość kodu.ProductCard przed importowaniem elementu useCheckout; foldery mają wyłącznie charakter dekoracyjny.Gdzie pojawiają się problemy
Gdy liczba plików osiąga około 100, albo gdy katalog components/ zawiera około 20 elementów, pojawia się sygnał ostrzegawczy. Problem objawia się w zachowaniu programistów, którzy zaczynają dodawać przedrostki do nazw plików, takie jak CartItem, CartDrawer i CartTotal, aby symulować grupowanie, którego nie zapewniają foldery. Te przedrostki są w istocie prośbą kodu o utworzenie folderów specjalnych dla poszczególnych funkcji.
Struktura oparta na funkcjach
Czym jest
Pliki są grupowane według ich funkcji. Każda funkcja ma swoje komponenty, hooki, wywołania API oraz stan w jednym miejscu, natomiast wspólne elementy znajdują się w specjalnej strefie, a funkcje są łączone na poziomie aplikacji bez konieczności wzajemnego importowania.
Dokumentacja React nazywała tę grupowanie funkcjami lub trasami i proponowała praktyczne sposoby określania ich granic. Ponieważ znaczenie pojęcia „funkcja” nie jest uniwersalne, to ty sam decydujesz o stopniu szczegółowości. Gdy nie przychodzi ci do głowy lista folderów najwyższego poziomu, twoi użytkownicy mogą opisać główne części produktu i wykorzystać swój model mentalny jako podstawę. To chyba najpraktyczniejsza rada w całym tym temacie: twoi użytkownicy już wiedzą, gdzie leżą granice poszczególnych funkcji.
W praktyce jest to Screaming Architecture przetłumaczone na React i obecnie stanowi standardowy wybór. W aplikacji przykładowej features/cart/ zawiera komponenty koszyka, useCart – API koszyka oraz cartStore; features/checkout/ zawiera CheckoutForm, useCheckout i checkoutApi; i tak dalej, przy czym Button, Modal oraz inne elementy bazowe znajdują się w wspólnej folderze components/.
Kto go używa
Najbardziej znaną implementacją referencyjną jest Bulletproof React autorstwa Alana Alickovica, która miała około 35,5 tys. gwiazdek na GitHubie i była nadal aktywnie utrzymywana w momencie pisania tego tekstu. Jest to najpowszechniej kopiowana struktura React, a dokument struktury projektu Bulletproof React warto przeczytać bezpośrednio, zamiast polegać na streszczeniach pochodzących od innych.
Zalecana struktura katalogu src/ składa się dokładnie z jedenastu folderów najwyższego poziomu, wymienionych poniżej wraz z opisem ich przeznaczenia. Należy zauważyć, że tylko jeden z nich, features, jest zorganizowany według domeny; pozostałe to wspólne warstwy, z których korzystają poszczególne elementy, oraz warstwa app, która łączy wszystko w całość.
src
+-- app # application layer (routes, app.tsx, provider.tsx, router.tsx)
+-- assets # static files such as images, fonts, etc.
+-- components # shared components used across the entire application
+-- config # global configurations, exported env variables etc.
+-- features # feature based modules
+-- hooks # shared hooks used across the entire application
+-- lib # reusable libraries preconfigured for the application
+-- stores # global state stores
+-- testing # test utilities and mocks
+-- types # shared types used across the application
+-- utils # shared utility functions
Wewnątrz każdej funkcji może znajdować się maksymalnie siedem segmentów: api, assets, components, hooks, stores, types oraz utils. Dokumentacja jasno wskazuje, że funkcja potrzebuje tylko tych segmentów, których faktycznie używa.
Dwie zasady realizują większość pracy:
- Brak importów między funkcjami. Bulletproof React zaleca unikanie sytuacji, gdy jedna funkcja importuje inną, i zamiast tego proponuje kompozycję funkcji na poziomie aplikacji, dzięki czemu każda z nich pozostaje niezależna, a kod jest łatwiejszy do zrozumienia.
- Jednokierunkowy przepływ. Kod powinien przepływać w jednym kierunku, od kodu wspólnego do funkcji, a następnie do aplikacji (
shared -> features -> app).
Oba te zasady można egzekwować za pomocą reguły ESLint import/no-restricted-paths. To właśnie ten krok egzekwowania jest często pomijany przez wiele zespołów, i to zazwyczaj powoduje, że ich granice z czasem się rozmywają.
Zmiana podejścia do plików barrel: Bulletproof React wcześniej zalecał pliki barrel, czyli plik
index.js, który ponownie eksportuje publiczną część funkcjonalności. Obecnie zaleca bezpośredni import plików, ponieważ pliki barrel mogą zakłócać proces tree shaking w Vite i powodować problemy z wydajnością. Jeśli nauczyłeś się tego podejścia z starszego przewodnika, zalecenia uległy zmianie. Ponieważ pliki barrel były sposobem, w jaki większość zespołów definiowała publiczną API funkcjonalności, ich odrzucenie oznacza, że granice funkcjonalności znajdują się teraz wyłącznie w konfiguracji lintera.
Zalety
- Kolokacja plików jest standardem. Jedna zmiana w koszyku dotyczy tylko jednej folderu, ponieważ pliki, które zmieniają się razem, znajdują się w tym samym miejscu.
rm -rf. Usuń katalog features/checkout/, popraw importy, z powodu których skompilator wydaje komunikaty błędu, i to wszystko. Jest to ważniejsze, niż się wydaje: kod, którego trudno usunąć, zazwyczaj nigdy nie zostaje usunięty.auth/, catalog/, cart/, checkout/ oraz orders/, zrozumie produkt, jeszcze zanim otworzy choćby jeden plik.Słabości
- "Cecha" nie ma ustalonej definicji, więc będziecie się o nią spierać.
CartTotal.jsxjest renderowany zarówno w koszyku, jak i podczas procesu płatności. Żadna z odpowiedzi nie jest poprawna; po prostu podejmujecie decyzję, a prawdopodobnie będziecie do niej wracać. - Kod współdzielony wymaga za każdym razem osądu. Obecnie
useDebouncejest używany tylko przy wyszukiwaniu w katalogu. Jeśli uczynimy go globalnym, folder z współdzielonymi hookami wypełni się elementami używanymi tylko raz. Lepiej zachować go wfeatures/catalog/hooks/, a gdy system fakturowania będzie tego potrzebował, trzeba będzie go skopiować, importować do innych funkcji (co jest zabronione) lub awansować go. - Rozdzielanie aplikacji na funkcje zbyt wcześnie szkodzi jeszcze bardziej niż abstrahowanie zbyt wcześnie. Umieszczenie aplikacji składającej się z 40 plików w pięciu folderach funkcji dodaje jedynie rutynę, a nie strukturę.
Gdzie powstają problemy
Zazwyczaj nie ma problemów, dlatego jest to standard. Pojawiają się one w dwóch sytuacjach. Po pierwsze, gdy pojedyncza funkcja staje się bardzo duża: folder checkout/ z 80 plikami to po prostu struktura oparta na typach na jednym poziom niżej. Po drugie, gdy powstaje rzeczywisty model domeny, który nie pokrywa się z funkcjami interfejsu. Te dwa czynniki prowadzą do następnych trzech struktur.
Atomic Design
Czym jest
Atomic Design to metodologia Brada Frosta, przedstawiona w artykule na blogu z czerwca 2013 roku i rozwinięta w książce z 2016 roku. Definiuje ona pięć poziomów. Przekładając jego definicje:
- Atomy to podstawowe elementy budulcowe, z których składa się każdy interfejs.
- Molekuły to dość proste grupy elementów interfejsu użytkownika, które działają razem jako jedna całość.
- Organizmy to bardziej złożone komponenty tworzone z molekuł, atomów, a nawet innych organizmów.
- Środowiska projektowe to struktury na poziomie stron, które układają komponenty w określonej strukturze i opisują podstawową strukturę treści.
- Strony to konkretne instancje środowisk projektowych wypełnione rzeczywistą, reprezentatywną treścią.
Detalem, który prawie wszyscy przeoczują, jest sam opis celu przez Frosta. Przedstawia on projektowanie atomowe jako model mentalny, a nie proces liniowy – sposób na postrzeganie interfejsu zarówno jako spójnej całości, jak i zbioru poszczególnych części jednocześnie. Nigdy nie chodziło o strukturę folderów; społeczność dodała je później.
Kto go używa
Tutaj rzeczywistość różni się od opinii publicznych. Istnieje niewiele wiarygodnych dowodów na to, że duże systemy projektowe React używają dosłownej taksonomii folderów atoms/, molecules/, organisms/. Na przykład Shopify Polaris i GitHub Primer nie stosują takiej nazewnictwa, mimo że wyniki wyszukiwania mogą sugerować inaczej. Wyraźnie potwierdzonym przykładem jest Pattern Lab – narzędzie stworzone przez samego Frosta w celu demonstracji tej metody.
Jako termin używany przez zespoły zajmujące się systemami projektowymi, Atomic Design ma ogromny wpływ. Jednak jeśli chodzi o dosłowną strukturę folderów w aplikacjach React w produkcji, dowody na jej stosowanie są znacznie słabsze, niż sugeruje jego sława. Jeśli ktoś twierdzi, że duża firma organizuje swoje foldery w React w ten sposób, należy poprosić o pokazanie repozytorium.
Zalety
- Daje projektantom i inżynierom wspólny język. To jest prawdziwa korzyść, a foldery nie odgrywają w tym żadnej roli. Gdy projektant odnosi się do określonego elementu, inżynier natychmiast rozumie jego ogólne zakresy, co jest cenne w zespołach zajmujących się systemami projektowymi.
- Zachęca do kompozycji. Hierarchia skłania do tworzenia małych elementów i ich łączenia, co stanowi dobrą praktykę w React, niezależnie od tego, gdzie znajdują się pliki.
- Ujawnia dostępne elementy bazowe. Folder
atoms/sprawia, że jest oczywiste, gdy masz trzy warianty przycisków, które powinny stanowić jeden element.
Słabości
- Klasyfikacja jest rzeczywiście trudna, a twórca to przyznaje.
Modal.jsxłączy tło, kontener, nagłówek oraz przycisk zamknięcia. Czy to grupa atomów, a zatem cząsteczka, czy złożony element samodzielny, a więc organizm? Frost ostatecznie opublikował zasadę ogólną: jeśli rozbicie komponentu daje podstawowe elementy takie jak obrazy, nagłówki, akapity i przyciski, to jest to cząsteczka; jeśli daje mniejsze komponenty takie jak karty, listy do udostępnienia lub obiekty multimedialne, to jest to organizm. Taka heurystyka istnieje tylko dlatego, że granica jest niejednoznaczna, a każda niejednoznaczna granica staje się przedmiotem ciągłych debat, które nie przynoszą żadnej wartości dla użytkownika.
Button i CheckoutForm, ale w ogóle nie ma żadnych opinii na temat useCheckout, checkoutApi, cartStore czy formatPrice. Połowa plików nie mieści się w tej taksonomii, więc zespoły muszą korzystać z elementów typu atomy, cząsteczki i organizmy oraz z układu opartego na typach dla wszystkiego innego, co powoduje dwa rodzaje problemów.organisms/, templates/, pages/, hooks/ i api/.Gdzie to zawodzi
Jeszcze przed opuszczeniem systemu projektowego. Sortowanie elementów podstawowych według stopnia złożoności w katalogach packages/ui/ lub shared/ui/ jest rozsądne. Gdy jednak zostanie zastosowane w całym aplikacji, od razu przestaje działać, ponieważ większość aplikacji składa się z elementów innych niż komponenty interfejsu użytkownika.
Sama postawa Frosta potwierdza tę interpretację: określa to mianem modelu mentalnego, a nie dogmatu, i zachęca zespoły do zmiany nazw poszczególnych etapów. Zespoły, które wymagają ścisłego stosowania koncepcji atomów i molekuł w aplikacji React, są bardziej sztywne niż osoba, która wynalazła tę metodę.
Struktura napędzana domeną
Czym jest
Domain-Driven Design pochodzi z książki Erica Evansa z 2003 roku, napisanej ponad dekadę przed pojawieniem się Reacta, i stanowi podejście do modelowania złożonych domen biznesowych w kodzie. Dwie z jej koncepcji znajdują zastosowanie również w frontendzie. Ograniczony kontekst to obszar oprogramowania, w którym określone terminy, definicje i zasady są stosowane spójnie. Powszechny język to zasada, zgodnie z którą kod powinien używać tego samego słownictwa co biznes.
W aplikacji React każda domena staje się folderem najwyższego poziomu, podzielonym wewnętrznie na warstwy:
domain/zawiera entity i obiekty wartości: czyste reguły biznesowe.application/zawiera przypadki użycia.infrastructure/zawiera klientów API oraz mechanizmy przechowywania danych.presentation/zawiera komponenty React.
Zależności wskazują wyłącznie do wewnątrz, więc domena nie wie nic o React ani HTTP.
Zobacz, co dzieje się z trudnymi plikami. formatPrice nie jest już pomocnikiem; staje się obiektem wartości Money znajdującym się w catalog/domain/. useAuth zmienia się w prosty wrapper warstwy prezentacyjnej otaczający przypadek użycia loginUser. React jest celowo umieszczany na skrajach struktury.
Kto go używa
Najlepszym praktycznym przewodnikiem jest książka Alexa Bespoyasova Clean Architecture on Frontend, która zawiera kompletną implementację w React i TypeScript w repozytorium bespoyasov/frontend-clean-architecture. Jego wyjaśnienie struktury warstwowej mówi, że domena to ta część, która pozostaje niezmienna bez względu na to, czy przechodzimy z React na Angular, czy zmieniamy konkretny przypadek użycia.
Warto również wiedzieć, że Manfred Steyer, być może najbardziej uznany zwolennik DDD w frontendzie, pracuje głównie z Angular. Model oparty na klasach i usługach w Angularze, z intensywną iniekcją zależności, jest bardziej naturalnym środowiskiem dla DDD niż funkcje i hooki w React. Zanim przyjmiesz jakieś szczegółowe wytyczne dotyczące DDD w frontendzie, sprawdź, dla którego frameworka zostały one napisane.
Taki styl występuje w branży fintech, ubezpieczeniowej, opieki zdrowotnej, logistyce oraz w oprogramowaniu B2B dla przedsiębiorstw, gdzie zasady biznesowe są skomplikowane i istniały na długo przed interfejsem użytkownika.
Zalety
- Logika biznesowa przetrwa framework. Entyteta
Cartz regułamiaddItemto zwykły TypeScript; zastąpienie React nie wpłynie na warstwę domenową. W przypadku produktu, który ma istnieć przez dekadę, stanowi to wartość dodaną. - Reguły można testować bez React. Nie ma nic do renderowania, żadnych hooków ani biblioteki testowej – tylko funkcje i twierdzenia, które działają w milisekundach. Przy złożonych regułach to samo może uzasadnić taką strukturę.
- Kod odzwierciedla rozmowy biznesowe. Gdy ekspert w danej dziedzinie mówi, że zamówienie nie może zostać wysłane, dopóki adres nie zostanie zweryfikowany, ta reguła ma dokładnie jedno miejsce przechowywania – plik o nazwie
Order.ts.
Słabości
- Kod szablonowy jest obszerny i stanowi główny koszt. Każda entytet zazwyczaj wymaga modelu domeny, DTO oraz mapera łączącego je ze sobą: trzy pliki i warstwa translacji dla tego, co aplikacja oparta na funkcjach prezentuje jako pojedynczą zapytanie API typowaną. Mnożymy to przez dwadzieścia entytetów.
- Większość aplikacji React nie posiada rzeczywistego domeny. To ocena, a nie udokumentowany fakt, ale stanowi kluczowy punkt tej sekcji. Typowa aplikacja React renderuje dane z API należącego do innej grupy lub usługi, a jej prawdziwa złożoność wynika ze stanu serwera, cache’owania, optymistycznych aktualizacji oraz warunków wyścigu. DDD nie zajmuje się żadnym z tych aspektów; biblioteka do zarządzania stanem serwera, tak jak React Query, lepiej rozwiązuje rzeczywisty problem niż warstwa domeny. Jeśli wasze entity to typowane odpowiedzi API bez invariantów, taka architektura kosztuje pieniądze i nic nie chroni.
- Frontend nie może naprawdę uciec od swojego frameworka. Obiecywana jest niezależność od frameworka, ale frontend zawsze jest powiązany z modelem renderowania, mechanizmami routingu, wzorcami zarządzania stanem, API przeglądarek oraz narzędziami do budowania aplikacji. Czysta architektura przenosi te ograniczenia na perypetie, zamiast je usuwać. To rzeczywiście korzyść, choć mniejsza, niż sugeruje jej przedstawienie.
- Konteksty UI są niejasne. W backendzie własność danych daje wyraźne granice. Frontend pokazuje to, czego potrzebuje ekran, a jedna strona często obejmuje trzy konteksty, więc granice stają się subiektywne i zmienne.
- Proces wdrożenia wymaga czasu. Programista używający Reacta, który nigdy nie miał do czynienia z DDD, może potrzebować kilku tygodni, aby stać się produktywny, a ktoś zawsze będzie pytał, po co istnieje ten maper.
Gdzie to zawodzi
Gdy domena istnieje tylko na papierze. DDD został zaprojektowany tak, by przynosić korzyści w obliczu rzeczywistej złożoności biznesowej. Jeśli twoje niezmienniki są rzeczywiste – takie jak pieniądze, umowy, regulowane procesy pracy czy wieloetapowe maszyny stanowe sterowane regułami – inwestycja się zwraca. Gdy zadanie polega na wyświetlaniu listy produktów z określonego punktu dostępu, kupujesz mnóstwo formalności tylko po to, by otoczyć operację fetch.
Korzystny test: czy potrafisz wymienić trzy reguły biznesowe ciekawsze od sprawdzania obowiązkowych pól? Jeśli nie, to co masz, to interfejs użytkownika, a nie domena.
Projektowanie w fragmentach funkcjonalnych
Czym jest
Projektowanie w fragmentach funkcjonalnych (FSD) to jedyna tutaj struktura, która stanowi formalną specyfikację, zawierającą wersjonowane wydania, dokumentację oraz oficjalnego lintera architektury o nazwie steiger. Pozostałe to konwencje; FSD jest spisany na piśmie.
Zawiera trzy poziomy hierarchii:
- Szaryfy to stałe foldery najwyższego poziomu:
app,pages,widgets,features,entitiesorazshared. - Kawałki to grupy w obrębie szaryfów, podzielone ze względu na znaczenie biznesowe, czyli to, co kod oznacza dla produktu lub aplikacji.
- Segmente to grupy w obrębie kawałków, podzielone ze względu na cel techniczny:
ui,api,model,liborazconfig.
Zasada, która je łączy, mówi, że moduł w kawałku może importować inne kawałki tylko wtedy, gdy znajdują się one na szaryfach wyraźnie poniżej jego własnego. Importy zawsze odnoszą się w dół, a dwa kawałki dzielące ten sam szaryf nigdy nie mogą importować się nawzajem. Moduły w tym samym szaryfie nie mają żadnego powiązania, co jest egzekwowane przez narzędzie do sprawdzania kodu, a nie dzięki dobrym intencjom.
Dwie korekty dotyczące twierdzeń krążących w innych źródłach są godne uwagi:
- Szara warstwa
processesjest przestarzała. Dokumentacja wskazuje, że obecne specyfikacje zalecają unikanie jej i przenoszenie jej zawartości do warstwfeaturesorazapp. Przewodniki, które nadal wymieniają siedem warstw, w tymprocesses, opisują starszą wersję. - Warstwa
pagesnie została uznana za przestarzałą; zyskała wyższy status. Wersja 2.1, opublikowana w listopadzie 2024 roku, nosi hasło „Strony są najważniejsze!”. Obecnie zaleca się umieszczanie większej ilości kodu wewnątrz stron: duże bloki interfejsu, formularze oraz logika przetwarzania danych, których nie wykorzystuje żadna inna strona, powinny pozostać w tej konkretnej stronie.
W wersji FSD aplikacji referencyjnej plik CheckoutForm.tsx znajduje się w katalogu pages/checkout/ui/, a nie w jakiejś osobnej części aplikacji. Podejście v2.0 z konieczności podzieliłoby ten plik na entity i features, natomiast wersja v2.1 zachowuje go w obrębie strony, dopóki inna strona faktycznie nie będzie go potrzebować.
Najciekawsza jest motywacja tej zmiany. Dekompozycja oparta na entityach z wersji v2.0 prowadziła do słabej spójności: śledzenie jednego przepływu użytkownika wymagało przechodzenia przez wiele folderów, a martwy kod był trudny do wykrycia. Twórcy przyznali w praktyce, że wcześniejsza wersja wymagała zbyt wielu decyzji architektonicznych przed napisaniem jakiegokolwiek kodu. Najcięższe krytyki wobec FSD pochodziły więc od jego samych autorów, którzy je również rozwiązali. Zasługuje się na uznanie za wdrożenie korekty, ale zespoły, które przyjęły v2.0, zapłaciły za tę lekcję.
Kto go używa
Sytuacja z adopcją wymaga szczerej oceny. W chwili pisania strona główna FSD zawiera sekcję poświęconą firmom korzystającym z FSD, w której nie wymienia się żadnych konkretnych firm, a zamiast tego zachęca się odwiedzających do zgłaszania swojej firmy.
Oficjalna strona z przykładami wymienia około 21 projektów, głównie dema i aplikacje hobbystyczne, takie jak klon RealWorld, gra Sudoku, symulowana sklep internetowy oraz mała gra w przeglądarce. Projekty, które wyglądają na oprogramowanie produkcyjne, są niewielkie – wśród nich znajdują się Roke.to, Moke Smoke, Moonlogs oraz rozszerzenie dla Chrome.
Mierzalne wskaźniki, według stanu na moment pisania, są skromne, ale rzeczywiste: około 297 repozytoriów na GitHubie używa tematu feature-sliced-design, repozytorium z dokumentacją ma około 2,3 tysiąca gwiazdek, a narzędzie do sprawdzania kodu około 388. Społeczność skupia się przede wszystkim wśród rosyjskojęzycznych programistów, co sprawia, że adopcja w języku angielskim wydaje się mniejsza, niż zapewne jest w rzeczywistości.
Z pierwotnych źródeł nie udało się potwierdzić, że jakakolwiek duża, rozpoznawalna zachodnia firma przyjęła FSD, a żadna oficjalna dokumentacja nie wymienia twórcy tej metodyki. W przypadku tak sformalizowanego rozwiązania jest to nietypowe i warto to wziąć pod uwagę przy podejmowaniu decyzji o jego przyjęciu.
Zalety
- Wyklucza dyskusje dotyczące umiejscowienia elementów, ponieważ specyfikacja już podjęła decyzję. Gdzie ma trafić ten plik? Specyfikacja podaje odpowiedź. W dużych zespołach to oszczędza prawdziwy czas, a tutaj FSD wyraźnie przewyższa podejście oparte na funkcjach, w którym definicja każdej funkcji jest ciągle ponownie otwierana.
- Granice są sprawdzane przez maszyny.
steigeroraz konfiguracja ESLint egzekwują zasady importu, a architektura podlegająca takim regułom rozkłada się znacznie wolniej niż ta bez nich. - Działa skutecznie w dużych zespołach wymagających niewielkiej koordynacji. Ustalone warstwy oraz brak importów między elementami pozwalają zespołom pracować równolegle bez konfliktów.
product to coś, co istnieje; add-to-cart to działanie wykonywane przez użytkownika. Są to różne rodzaje kodu, a większość układów je ze sobą myli.Słabości
- Krzywa uczenia się jest stroma, a strona motywacyjna samego FSD przyznaje się do tego problemu, wskazując, że istniejące podejścia wymagają wysokich warunków wejścia, co utrudnia zatrudnianie. FSD nie jest wyjątkiem: warstwy, kawałki i segmenty to trzy taxonomie, których trzeba się nauczyć, zanim można umieścić pojedynczy plik.
- Zmiana na wersję v2.1 sygnalizuje zmiany w rozwoju produktu. Zmieniła się podstawowa jednostka dekompozycji, więc wszyscy pracujący według specyfikacji muszą dostosować się do tych zmian.
- Sama dokumentacja określa to jako nadmierne rozwiązanie dla małych projektów, zaznaczając, że jest to niepotrzebne w przypadku krótkotrwałych projektów lub gdy szybkość jest ważniejsza niż jakość kodu.
- Dowody na skuteczność tej metody są słabe. To w porządku dla projektu osobistego, ale trudniej jest to przekonać, gdy interesariusz pyta, kto jeszcze zarządza tym w środowisku produkcyjnym.
- Migracja starego kodu jest kosztowna. Dodawanie nowych warstw i elementów do istniejącego kodu to duży projekt, który przynosi niewielkie widoczne korzyści.
Gdzie to zawodzi
W małych zespołach i aplikacjach krótkotrwałych, gdzie formalności przeważają nad rzeczywistymi korzyściami. FSD jest przeznaczony do dużych, trwałych produktów z wieloma uczestnikami, którzy naprawdę potrzebują jasno określonych granic. W przypadku czegokolwiek mniejszego, podejście oparte na funkcjach przynosi większość korzyści przy użyciu znacznie mniej zasad.
Struktura modułowa i monorepo
Czym to jest
W odróżnieniu od pozostałych przypadków, nie chodzi tu o organizację katalogu src/, dlatego omawia się to na końcu. Chodzi o strukturę repozytorium: kilka aplikacji oraz współdzielonych pakietów w jednym repozytorium, koordynowanych przez narzędzie do wykonywania zadań.
Dokumentacja Turborepo opisuje kanoniczną strukturę: najpierw umieszcza się aplikacje i usługi w katalogu apps/, a wszystko inne, takie jak biblioteki i narzędzia, w katalogu packages/. Zapoznaj się z przewodnikiem Turborepo dotyczącym organizacji repozytorium.
Każda aplikacja znajdująca się w katalogu apps/ wymaga nadal wewnętrznej struktury z jednych z sześciu dostępnych opcji. Monorepo jest niezależne od sposobu organizacji pojedynczej aplikacji, a mylenie tych dwóch koncepcji jest najczęstszym nieporozumieniem w tym temacie.
W momencie pisania tego tekstu głównymi narzędziami były turbo (2.10.x, około 73 milionów pobierania miesięcznie), nx (23.x, około 38 milionów) oraz lerna (9.x, około 7,5 miliona). Sprawdź aktualne dane przed poleganiem na nich. Wbrew powszechnemu przekonaniu Lerna nie jest przestarzałe. W 2022 roku Nx przejęło jego zarządzanie, gdy istniało ryzyko, że przestanie być utrzymywany. Nadal jest dostarczany, a obecnie zadania związane z wykonywaniem i przechowywaniem w pamięci cache są przekazywane do Nx, które skupia się na wersjonowaniu i publikowaniu.
Kto go używa
W dyskusjach na temat monorepo często wymienia się Google, Meta i Microsoft, co jest mylące. Są to monorepo na poziomie całych firm, a nie monorepo dla Reacta, i nic nie mówią o twojej aplikacji React.
Liczby wciąż są interesujące. Zgłoszono, że repozytorium Google zawiera około miliarda plików o łącznej wielkości 86 TB (Potvin i Levenberg, Communications of the ACM, lipiec 2016). Meta opisuje swój repozytorium zarządzany przez Sapling jako zawierający dziesiątki milionów plików, komitetów oraz gałęzi. Według Briana Harry’ego z maja 2017 roku repozytorium Windows w Microsoft zawierało około 3,5 miliona plików o łącznej wielkości 300 GB, nad którymi pracowało około 4000 inżynierów.
Każda z tych firm musiała stworzyć własne narzędzia, aby sobie poradzić: Google opracował Piper i Bazel, Meta zbudowała Sapling i EdenFS, a Microsoft napisał GVFS. Traktuj to jako sygnał kosztowy, a nie rekomendację. Jeśli monorepo wymaga dedykowanego zespołu ds. infrastruktury, aby funkcjonować, mówi to coś o monorepo.
Relevantne, sprawdzone przykłady z React są inne:
- Repozytorium
next.jsfirmy Vercel to prawdziwy monorepo dla React, zawierający katalogiapps/ipackages/na najwyższym poziomie, przestrzenie robocze pnpm oraz 19 pakietów, w tymnext,create-next-appieslint-config-next. - Shopify przechowuje swoje aplikacje React Native w jednym monorepo obejmującym funkcjonalności Shop, Inbox, Point of Sale, Shopify Mobile oraz Local Delivery. Ich zespół inżynieryjny wyjaśnił w wrześniu 2021 roku, że pojedynczy monorepo obniża koszty konserwacji, ponieważ istnieje jeden proces do ulepszenia i optymalizacji zamiast obsługi wielu oddzielnych pakietów i repozytoriów.
To jest rzeczywisty powód stosowania monorepo, przy czym chodzi tu o proces konserwacji, a nie o organizację kodu.
Zalety
- Zmiany między pakietami są atomowe. Zmiana pliku
packages/ui/Buttonoraz wszystkich trzech aplikacji, które go używają, odbywa się w ramach jednego commita, jednej prośby o pull request, jednej oceny oraz jednego uruchomienia CI. W przypadku oddzielnych repozytoriów wymaga to podniesienia wersji, publikacji oraz trzech aktualizacji zależności. To właśnie dlatego istnieją monorepozytoria. - Dzielenie się zasobami odbywa się w ramach wyraźnych granic. Katalog
packages/uiposiada własny plikpackage.jsonoraz jasno określoną publiczną API, a granica pakietu stanowi najsilniejszy mechanizm dostępny w JavaScript. - Istnieje tylko jedna wersja wszystkiego: żadna aplikacja nie pozostaje przy React 18, podczas gdy inna używa wersji 19 – istnieje jeden plik lockfile oraz jedno źródło prawdy.
- Wielkie refaktoryzacje stają się możliwe. Można przemienić nazwę API we wszystkich aplikacjach, które go używają, w ramach jednego commita, przy czym kompilator sprawdzi wynik.
Słabości
- Narzędzia stanowią rzeczywisty i trwały obciążenie: przestrzenie robocze, grafy zadań, odległe cacheowanie oraz matryce CI. Sama dokumentacja Turborepo przyznaje istnienie problemu skalowalności, ponieważ każda przestrzeń robocza generuje własne zadania testowe, sprawdzające format i budujące kod, a pojedyncze monorepo może mieć ich tysiące. Turborepo zostało stworzone, aby rozwiązać problemy powstające w wyniku używania monorepo.
- Proces CI staje się wolniejszy i droższy, zanim w końcu przyspieszy. Bez prawidłowo skonfigurowanego wykrywania zmian i cacheowania każda prośba o pull request uruchamia wszystkie operacje.
- Proces wdrożenia jest trudniejszy. Przed pierwszym commitem nowy pracownik musi nauczyć się, jak rozwiązuje problemy przestrzeń robocza, jak funkcjonuje graf zadań oraz jak zachowuje się system cacheowania.
packages/ui ma trzech użytkowników, każda zmiana niesie ryzyko regresji w tych trzech aplikacjach. Zespoły często reagują dodawaniem właściwości i wariantów, aż wspólny komponent staje się językiem konfiguracji, którego nikt nie rozumie.Gdzie to zawodzi
Gdy istnieje tylko jedna aplikacja. Monorepo, w którego folderze apps/ znajduje się pojedyncza aplikacja, musi płacić pełny koszt narzędzi bez żadnej korzyści: nie ma kodu do udostępnienia ani zmian między aplikacjami, które można by przeprowadzić atomowo.
Jeśli chodzi o progi, nie istnieją żadne autorytatywne dane. Turborepo i Nx nie publikują informacji na temat wielkości zespołu ani liczby aplikacji, przy których wdrożenie staje się opłacalne. Dokładne liczby, które krążą w internecie, takie jak konkretna liczba inżynierów wymagana rzekomo do korzystania z Turborepo, pochodzą z niskiej jakości treści SEO, a nie z dokumentacji dostawcy czy badań. Każdy, kto podaje dokładny próg, po prostu go wymyśla.
Rozsądna zasada ogólna, przedstawiona jako opinia, a nie fakt: wdrożenie monorepo warto rozważyć, gdy masz dwie rzeczywiste aplikacje, które faktycznie dzielą kod, lub jedną aplikację wraz z systemem projektowym wykorzystywanym przez zespoły zewnętrzne. W innych przypadkach koszty przewyższają korzyści, a twoja sytuacja może zmienić tę granicę.
React Server Components nie określają struktury folderów
Wiele ostatnich artykułów twierdzi, że komponenty serwerowe React zmieniają sposób strukturyzowania folderów, zazwyczaj zalecając oddzielne katalogi components/server/ i components/client/. Oficjalna dokumentacja nie potwierdza tego.
Dyrektywa use client oznacza granicę w grafie modułów, a nie w drzewie katalogów. Odnoszący się do tego przewodnik renderowania Next.js opisuje use client jako deklarację granicy pomiędzy grafami modułów serwera i klienta i wyjaśnia, że gdy plik zawiera tę dyrektywę, jego importy oraz komponenty, które bezpośrednio renderuje, trafiają do paczki klienta.
To drugi punkt jest kluczowy. Granica rozprzestrzenia się poprzez importy, a nie foldery. Plik znajdujący się w katalogu client/ nie jest komponentem klienta ze względu na swoją lokalizację, natomiast plik w katalogu server/ nie otrzymuje żadnej ochrony dzięki temu nazewnictwu. To, co decyduje o wyniku, to dyrektywa w połączeniu z łańcuchem importów.
Jeśli chcesz zapewnić przestrzeganie zasad, narzędziami są pakiety npm typu server-only i client-only, które powodują niepowodzenie procesu budowania, gdy kod przekroczy niewłaściwą granicę. Foldery tego nie zapewniają.
Oficjalne wytyczne w rzeczywistości sugerują coś przeciwnego: należy przenieść instrukcję 'use client' niżej w strukturze, w kierunku komponentów najniższego poziomu, aby utrzymać mały rozmiar pliku klienta. Podział na foldery server i client na najwyższym poziomie zachęca do działania w przeciwnym kierunku.
Kilka konwencji App Router rzeczywiście wpływa na organizację kodu i jest istotnych:
- Grupy tras, zapisywane jako
(foldera), organizują trasy bez zmiany adresu URL. - Foldery prywatne, zapisywane jako
_folder, całkowicie wykluczają się z mechanizmu routingu. - Kolokacja jest domyślnie bezpieczna, ponieważ trasa staje się dostępna publicznie dopiero po dodaniu pliku
page.jslubroute.js. Komponenty, testy i narzędzia mogą znajdować się bezpośrednio w segmentach trasy, nie stając się samymi trasami, więc foldery prywatne nie są w tym przypadku konieczne.
Następnie Next.js jasno stwierdza, że nie ma żadnych preferencji co do sposobu organizacji i umieszczania plików projektu. Jego dokumentacja przedstawia trzy strategie organizacyjne bez ich rankowania.
Jak wybrać
Oto decyzja w skondensowanej formie, z podaniem najważniejszych uzasadnień:
- Mniej niż około 30 plików albo nadal odkrywasz problem: trzymaj się prostoty. Struktura oparta na rzeczywistych wzorcach zmian jest lepsza od tej domyślonej od samego początku, tak jak sugerowały dokumentacje Reacta.
- Mniej niż około 50 plików, mały zespół i krótki czas trwania projektu: podejście oparte na typach jest w porządku. Jest niemodne, ale to nie ma znaczenia; migracja kosztowałaby więcej, niż zaoszczędzi.
- Cokolwiek poważnego i długowiecznego: wybierz podejście oparte na funkcjach. To powinno być standardem. Spełnia wymogi koncepcji Screaming Architecture, uwzględnia zasadę współlokalizacji, ułatwia usuwanie funkcji oraz obsługuje granice narzucone przez narzędzia lintingu. Zacznij od jedenastu folderów w Bulletproof React, wcześnie dodaj strefy
import/no-restricted-pathsi pomiń pliki typu barrel.
apps/ i packages/, zarządzanego przez Turborepo lub Nx, i pozwól każdej aplikacji wybrać własną wewnętrzną strukturę z tej listy.Jedna zasada ma najwyższy priorytet nad wszystkimi innymi: podejścia te łączą się ze sobą. Rzeczywiste bazy kodu je mieszają, a dokumentacja Reacta potwierdza to, zaznaczając, że większe projekty w praktyce często stosują połączenie różnych podejść, więc wybór „odpowiedniego” na początku nie jest tak ważny.
Całkowicie rozsądnym ustawieniem do produkcji jest monorepo zawierające aplikacje oparte na funkcjach, pakiet systemu projektowego zorganizowany według mniej więcej spójnych kategorii oraz warstwę domenową w obszarze obejmującym rzeczywiste zasady biznesowe. Nikt nie ocenia twojej architektury pod kątem jej czystości.
Główne wnioski
Ludzie najbliżej tych konwencji traktują je elastycznie i ciągle je poprawiają publicznie. Frost określa Atomic Design jako model myślowy, a nie dogmat. Dokumentacja React ograniczyła czas podjęcia decyzji do pięciu minut, a następnie usunęła tę stronę. Next.js i Vite odmawiają zajęcia stanowiska. FSD zmienił swoją podstawową jednostkę dekompozycji w wersji v2.1, a Bulletproof React odwrócił swoją zalecenie dotyczące plików barrel. Zmienili kurs, ponieważ słuchali rzeczywistych opinii zamiast bronić swojego stanowiska.
Pozostają zatem dwie trwałe zasady, obie starsze od każdego układu omawianego tutaj:
- Screaming Architecture: foldery najwyższego poziomu powinny pokazać nowemu inżynierowi, czym zajmuje się produkt, a nie jakie frameworki jest on wykorzystywany.
- Colocation: elementy, które zmieniają się razem, powinny znajdować się jak najbliżej siebie, o ile to możliwe.
Każda struktura opisana w tym przewodniku stara się spełniać te dwie zasady na określonej skali. Wybierz tę najtańszą, która spełnia je dla aplikacji, którą masz obecnie, a nie tę, jaką planujesz mieć. Sygnały wskazujące, że nadszedł czas na zmianę, są konkretne: przedrostki nazw plików zastępujące foldery, cztery otwarte foldery przy jednej zmianie lub godzina stracona na debatę o tym, czy coś stanowi cząsteczkę. Działaj na podstawie tych sygnałów, gdy się pojawią, a nie wcześniej.