ProgramowanieBudujemy aplikację
#aplikacja#projekt#kodowanie#szkielet#struktura
Posted on 8.01.2025
Witajcie w nowym roku!
Zaczniemy z przytupem, czyli kontynuując budowę naszej aplikacji służącą do sprawdzania jakości powietrza w okolicy.
Najsampierw przygotujemy wstępną strukturę folderów i jakieś początkowe pliki.
Zaczynamy!
Stan obecny
Zobaczmy najpierw, jak nasza aplikacja wygląda w tym momencie. Przejdźmy na główny branch (u mnie jest to develop
) i zerknijmy w edytor.
smoggy_foggy_2.0/
├── .git/
├── .husky/
├── .idea/
├── .storybook/
├── coverage/
├── node_modules/
├── public/
│ ├── next.svg
│ └── vercel.svg
├── src/
│ └── app/
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.tsx
│ ├── page.module.css
│ └── page.tsx
├── .eslintrc.json
├── .gitignore
├── .lintstagedrc.js
├── .gitignore
├── .prettierrc
├─ ─ jest.config.ts
├── next.config.mjs
├── next-env.d.ts
├── package.json
├── package-lock.json
├── README.md
└── tsconfig.json
Jak widać mamy całkiem sporo rzeczy, które zostały domyślnie utworzone podczas instalacji NextJS.
Stwórzmy nowy branch poleceniem git switch -c clean-project
i wyczyśćmy nasz projekt ze wszelkich zbędnych rzeczy. Usuwamy pliki
- public/next.svg
- public/vervel.svg
- src/app/favicon.ico
- src/app/globals.css
- src/page.module.css
Teraz musimy jeszcze edytować kilka plików, które zostawiliśmy.
src/app/layout.tsx
import type { Metadata } from 'next'
import { Inter } from 'next/font/google' //linijka do usunięcia
import './globals.css' //linijka do usunięcia
const inter = Inter({ subsets: ['latin'] }) //linijka do usunięcia
export const metadata: Metadata = {
//edytujemy treść
title: 'Create Next App',
description: 'Generated by create next app',
}
//preferuję używanie funkcji strzałkowych, ale zostawienie notacji function() nie jest błędem
export default function RootLayout({
children,
}: Readonly<{
//niby można zostawić, ale ja preferuję import modułów z Reacta, także poniżej zamieniamy React.ReactNode na ReactNode i importujemy ReactNode z React na górze pliku
children: React.ReactNode
}>) {
return (
<html lang="en">
//usuwamy className
<body className={inter.className}>{children}</body>
</html>
)
}
Teraz plik powinien wyglądać następująco:
import type { Metadata } from 'next'
import { ReactNode } from 'react'
export const metadata: Metadata = {
title: 'Smoggy Foggy',
description: 'Sprawdź jakość powietrza',
}
const RootLayout = ({
children,
}: Readonly<{
children: ReactNode
}>) => {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
export default RootLayout
Na razie tak może zostać. Przechodzimy do kolejnego pliku.
src/app/page.tsx
import Image from 'next/image' //linijka do usunięcia
import styles from './page.module.css' //linijka do usunięcia
//podobnie ja w poprzednim pliku zamieniam na funkcję strzałkową - ale jak chcie, możecie zostawić bez zmian
export default function Home() {
return (
//usuwamy className i czyścimy całą zawartość tagów main
<main className={styles.main}>
<div className={styles.description}>
<p>
Get started by editing
<code className={styles.code}>src/app/page.tsx</code>
</p>
<div>
<a
href="https://vercel.com?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
By{' '}
<Image
src="/vercel.svg"
alt="Vercel Logo"
className={styles.vercelLogo}
width={100}
height={24}
priority
/>
</a>
</div>
</div>
<div className={styles.center}>
<Image
className={styles.logo}
src="/next.svg"
alt="Next.js Logo"
width={180}
height={37}
priority
/>
</div>
<div className={styles.grid}>
<a
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className={styles.card}
target="_blank"
rel="noopener noreferrer"
>
<h2>
Docs <span>-></span>
</h2>
<p>Find in-depth information about Next.js features and API.</p>
</a>
<a
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className={styles.card}
target="_blank"
rel="noopener noreferrer"
>
<h2>
Learn <span>-></span>
</h2>
<p>Learn about Next.js in an interactive course with quizzes!</p>
</a>
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className={styles.card}
target="_blank"
rel="noopener noreferrer"
>
<h2>
Templates <span>-></span>
</h2>
<p>Explore starter templates for Next.js.</p>
</a>
<a
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
className={styles.card}
target="_blank"
rel="noopener noreferrer"
>
<h2>
Deploy <span>-></span>
</h2>
<p>
Instantly deploy your Next.js site to a shareable URL with Vercel.
</p>
</a>
</div>
</main>
)
}
Udało nam się bardzo znacząco odchudzić plik:
const Home = () => {
return <main>Lorem Ipsum</main>
}
export default Home
Teraz wystarczy uruchomić komendę npm run dev
. Wszystko powinno działać, a po przejściu w przeglądarce do http://localhost:3000 powinno nam się ukazać nasze znajome Lorem Ipsum.
Zapisujemy i commitujemy wszystkie zmiany, po czym wypychamy branch na GitHuba.
git add .
git commit -m"Remove default files"
git push
Jeśli nie chcecie przy pushu każdego nowego brancha musieć wykonywać git push --set-upstream origin <nazwa-brancha>
, to zapraszam do poradnika.
Pierwszy etap zakończony. Proponuję zmergować nasz nowy branch na GitHubie, przejść lokalnie na develop i zaciągnąć zmiany.
git switch -
git pull
Wszystko, co zrobiliśmy w tej części można podejrzeć w repozytorium na branchu clean-project.
Planowanie struktury
Istnieje kilka konwencji, jakie można przyjąć, planując strukturę naszej aplikacji. W zasadzie pójście na żywioł i segregowanie wszystkiego na bieżąco też nie jest zabronione. Aplikacja nam się z tego powodu nie wywali.
Jednak nie wszystko, co nie jest zakazane, jest rozsądne. A chaos w projekcie bardzo szybko sprawi, że pożałujemy poskąpienia zawczasu odrobiny uwagi na przemyślenie struktury.
Jak przed chwilą wspomniałem, nie istnieje jedna - jedyna właściwa struktura projektu. React w żaden sposób nie wymusza na nas tego, jak zorganizujemy nasze pliki. W zasadzie można też powiedzieć, że nie istnieje też jakieś podejście, które byłoby zawsze złe. Nawet wrzucenie wszystkich plik ów luzem do folderu src/
może mieć sens, jeśli projekt jest mały i tych plików nie będzie wybitnie dużo.
W pierwszej kolejności musimy sobie zatem odpowiedzieć na pytanie, jaki ma być cel naszej struktury. Naturalnie ułatwi to nam (oraz tym, którzy będą z nami nad aplikacją pracować) poruszanie się w projekcie. W największym skrócie - ustrukturyzowanie plików ma uprościć nam pracę.
Musimy wziąć pod uwagę, że będziemy pracować z NextJS. Framework ten nie tyle wymusza na nas stosowanie konkretnej struktury, co sugeruje pewne rozwiązania, mające usprawnić pracę przy aplikacji. Jest to związane z routingiem w NextJS, o którym porozmawiamy innym razem.
Niemniej najczęściej spotykana konwencją jest podział projektu na kilka katalogów, których nazwy podlegają pewnym niepisanym zasadom.
Najważniejsze katalogi to te, w których będziemy trzymać główne widoki (czy strony - jak kto woli) oraz komponenty. Normalnie pierwszy typ plików nazwalibyśmy pages/
lub views/
, zaś drugi components/
. Ponieważ jednak pracujemy z NextJS, to strony będziemy trzymać w katalogu app/
, który został utworzony przy instalacji.
Zastanówmy się, jak posegregować pozostałe pliki. Z pewnością będziemy mieć jakieś elementy graficzne - i te proponuję wrzucać do katalogu assets/. Z pewnością podczas pracy napiszemy niejedną funkcję pomocniczą. Do przechowywania tego typu plików najczęściej używa się katalogów helpers/
, library/
, lib/
lub utils/
. Czasami można też spotkać projekty, gdzie te katalogi występuje równolegle, np. utils/
, helpers/
oraz lib/
.
Nasz projekt będzie raczej projektem średnim, dlatego nie chcę tworzyć zbyt wielu głównych katalogów, więc wybieram opcję pośrednią - czyli katalog utils/ z podkatalogami lib/
oraz helpers/
.
Nasza aplikacja ma być aplikacją reactową, zatem z pewnością pojawią się w niej customowe hooki. Te wrzucimy do katalogu hooks/
.
Bardzo ważną funkcjonalnością będzie globalny stan aplikacji. Tutaj mamy do wyboru katalog context/
, state/
lub store/
. W przypadku używania wbudowanego w React useContext zdecydowałbym się na context/
właśnie, jednak - ponieważ my wykorzystamy Redux, to pliki związane ze stanem trafią do katalogu state/
.
Coś jeszcze? Owszem. Można zdecydować się na katalog __tests__
do przechowywania plików z testami - my jednak testy rozwiążemy w inny sposób. Ale jest jeszcze jeden katalog, który chciałbym, żebyśmy stworzyli. Mianowicie mocks/
, gdzie trafią pliki ze zmockowanym api.
Dobrze, mamy podstawy. Trzeba się jeszcze zastanowić, jak katalogować nasze komponenty. Owszem, można wszystkie, jak leci, wrzucić do katalogu components/
. Jednak ponownie - kiedy aplikacja zacznie się rozrastać, to możemy mieć w końcu poważny problem, żeby się w tych komponentach połapać.
Osobiście bardzo lubię podejście Atomic Design. W skrócie jest to metodologia polegająca na podzieleniu interfejsu aplikacji na pięć poziomów, gdzie każdy jest coraz bardziej skomplikowany. Pozwala to bardzo dobrze i intuicyjnie zorganizować nasze komponenty. Ponadto podejście to świetnie współgra ze Storybookiem.
Te poziomy to
- atoms (atomy),
- moleculees (molekuły lub cząsteczki),
- organisms (organizmy),
- templates (szablony),
- pages (strony).
Jak już ustaliliśmy, strony aplikacji będą przechowywane w katalogu app/
, dlatego zostają nam cztery poziomy, które teraz pokrótce omówimy.
Atoms
Najmniejsze, podstawowe komponenty, jakie tworzą naszą aplikację. Dobrym przykładem może być komponent Button czy Paragraph. Teoretycznie wszystkie elementy naszego interfejsu powinniśmy móc rozbić do poziomu atomów.
Molecules
Elementy nieco bardziej skomplikowane od atomów, zazwyczaj będące połączeniem kilku z nich. Każda molekuła powinna spełniać wymogi reużywalności oraz zasadę pojedynczej odpowiedzialności (single responsibilty prenciple).
Przykładową molekułą może być karta zawierająca zdjęcie z opisem, lub input z labelem.
Organisms
Organizmy są już znacznie bardziej rozbudowanymi komponentami. Mogą być zbudowane z molekuł, atomów, ich połączenia lub nawet z połączenia innch organizmów. Najważniejsze, aby połączenie tych elementów miało sens. Przykładem organizmów może być header zawierający logo, menu oraz wyszukiwarkę.
Templates
Szablony zawierają odwzorowanie całych stron lub ich istotnych fragmentów. Zbudowane są z połączonych organizmów. Często spotykanym szablonem może być układ
<Header />
<SiteBar />
<MainContainer>
{children}
</MainContainer>
<Footer />
Dobra, bierzmy się do roboty i stwórzmy nasze katalogi.
Nasz projekt powinien teraz wyglądać następująco:
smoggy_foggy_2.0/
├── .git/
├── .husky/
├── .idea/
├── .storybook/
├── coverage/
├── node_modules/
├── public/
│ ├── next.svg
│ └── vercel.svg
├── src/
│ ├── app/
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── assets/
│ ├── components/
│ │ ├── Atoms/
│ │ ├── Molecules/
│ │ ├── Organisms/
│ │ └── Templates/
│ ├── hooks/
│ ├── mocks/
│ ├── store/
│ └── utils/
│ ├── helpers/
│ └── lib/
├── .eslintrc.json
├── .gitignore
├── .lintstagedrc.js
├── .gitignore
├── .prettierrc
├── jest.config.ts
├── next.config.mjs
├── next-env.d.ts
├── package.json
├── package-lock.json
├── README.md
└── tsconfig.json
Na razie wydaje się to zbytecznym skomplikowaniem projektu. Ale uwierzcie, że z biegiem czasu ustrukturyzowanie projektu zawczasu zacznie przynosić coraz więcej korzyści.
Zwróciliście może uwagę, że przed przystąpieniem do tworzenia struktury nie tworzyliśmy nowego brancha. I nie, nie było to spowodowane moim przeoczeniem. Zwyczajnie nie miałoby to najmniejszego sensu. Jeśli teraz, po utworzeniu tych wszystkich folderów wpiszecie sobie w konsoli git status
to otrzymacie informację, że
On branch develop
Your branch is up to date with 'origin/develop'.
nothing to commit, working tree clean
Ale jak to? Przecież wprowadziliśmy konkretne zmiany!
No właśnie z punktu widzenia gita nie. Nie chcę się wdawać w dywagacje na temat tego, jak konkretnie działa git, ale utworzenie pustego katalogu nie jest rejestrowane jako zmiana. Dlatego trzeba pamiętać, że dopóki w katalogu nie wyląduje jakiś plik, to katalog ten będzie dostępny jedynie lokalnie i nie trafi do naszego repozytorium zdalnego.
Na sam koniec chciałem jeszcze wrócić do tego, że nie stworzyliśmy katalogu na testy. Otóż nasze komponenty nie będą przechowywane bezpośrednio w podkatalogach Atomic Design. Każdy z komponentów będzie miał swój własny katalog. Czemu? Bo w tym katalogu, poza samym komponentem, będą także style oraz testy. Jak to wygląda w praktyce, zobaczymy już następnym razem, na razie wyobraźmy sobie przykładowy komponent Button
. Jego struktura wyglądałaby następująco:
components/
└── Atoms/
└── Button/
├── Button.tsx
├── Button.styles.ts
└── Button.test.tsx
Cóż, dziś za wiele nie pokodziliśmy. Ale mimo wszystko odwaliliśmy kawał dobrej roboty, która już niedługo przyniesie wymierne korzyści.
Tradycyjnie już zapraszam na mojego twixera.
Do następnego!