Podstawa React gotowa do użycia w produkcji: co faktycznie robi każdy pakiet.
Ustaw Vite, Tailwind v4, Redux Toolkit, React Router, Jest i Prettier dla aplikacji React oraz zrozum, dlaczego każdy pakiet i linia konfiguracji jest tam obecna.
Wykonanie polecenia npm create vite daje aplikację React, która się renderuje, ale nie jest to rozwiązanie przeznaczone dla prawdziwych użytkowników: brakuje systemu stylizacji, wspólnego stanu, routingu, testów oraz ustalonego formatu kodu. Ten przewodnik krok po kroku buduje te brakujące elementy za pomocą Tailwind CSS, Redux Toolkit, React Router, Jest w połączeniu z React Testing Library oraz Prettier. Dla każdego pakietu odpowiada na dwa pytania: co on faktycznie robi i co się zepsuje, jeśli go pominąć? Pod koniec będziesz miał gotową bazę do rozwijania nowych funkcjonalności, a co równie ważne – będziesz w stanie czytać swój własny plik package.json i wyjaśniać każdą linię.
Zestaw narzędzi w pigułce:
- Tailwind CSS do stylizacji
- Redux Toolkit do zarządzania wspólnymi danymi aplikacji
- React Router do nawigacji między stronami
Część z tych narzędzi można zainstalować w jednej linii. Inne kryją zaskakujące szczegóły; na przykład „React Testing Library” to w rzeczywistości trzy pakiety pełniące trzy różne funkcje.
Zacznij od nowego projektu Vite przy użyciu szablonu React + TypeScript:
npm create vite@latest react-production-stack -- --template react-ts
cd react-production-stack
npm install
Tailwind CSS: stylizacja wprowadzana od razu
Pakiety: tailwindcss, @tailwindcss/vite
Stylizacja dotyczy każdego komponentu, więc sensowne jest sprawdzenie jej działania przed dodaniem czegokolwiek innego.
npm install tailwindcss @tailwindcss/vite
To instaluje oba pakiety jako zwykłe zależności, a nie devDependencies. Mówiąc ściśle, żaden z pakietów nie działa w przeglądarce: plugin Vite wykonywa swoje zadania podczas budowania, a do pliku końcowego trafia jedynie wygenerowany CSS. Dlatego wiele zespołów umieszcza je w kategorii devDependencies, a przy aplikacji jednostronicowej spakowanej w jeden plik obie opcje dają ten sam wynik. Wybierz jedną konwencję i trzymaj się jej konsekwentnie.
Następnie zarejestruj plugin obok pluginu React w konfiguracji Vite:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [react(), tailwindcss()],
})
Potem zastąp zawartość src/index.css jednym importem. Oto cały plik:
/* Tailwind v4 is CSS-first. No config file, no content globs. */
@import 'tailwindcss';
To naprawdę wszystko, co potrzeba do konfiguracji. Tailwind v4 opiera się na CSS: nie ma pliku tailwind.config.js ani listy globalnych nazw elementów, ponieważ sam analizuje pliki źródłowe w poszukiwaniu nazw klas.
Weryfikacja działania
Tymczasowo dodaj kilka klas pomocniczych do nagłówka w pliku App.tsx, na przykład text-3xl font-bold text-blue-600, uruchom npm run dev i sprawdź, czy nagłówek się zmieni. Jeśli tak, oznacza to, że wtyczka i import CSS są połączone.
Dlaczego klasy pomocnicze zamiast oddzielnych plików stylów
Tailwind przechowuje style bezpośrednio w elementach, na które mają wpływ. W przypadku oddzielnego pliku CSS łatwo jest edytować komponent, a potem zapomnieć o jego pliku stylów, co powoli prowadzi do gromadzenia się niepotrzebnych i przestarzałych reguł. Szczególnie panele kontrolne wielokrotnie używają tych samych elementów (karty, odznaki, przyciski), a budowanie ich z jednego wspólnego zestawu klas pomocniczych zapewnia spójność wizualną przy mniejszej ilości kodu do utrzymania. Kompromisem jest zmiana nawyków: zamiast wymyślać nazwy klas takie jak .card-header-active, każdy element składa się z małych, z góry zdefiniowanych klas.
Redux Toolkit: magazyn danych i most do React
Pakiety: @reduxjs/toolkit, react-redux
Te dwa pakiety łatwo pomieszać, ale pełnią różne funkcje:
@reduxjs/toolkitto sam magazyn danych: przechowuje dane aplikacji i wprowadza w nich aktualizacje.react-reduxto połączenie z React: dostarcza komponent<Provider>oraz hooki, których używają komponenty do odczytywania i aktualizowania tych danych.
Potrzebujesz obu, ponieważ żaden z nich nie może wykonać zadania drugiego.
npm install @reduxjs/toolkit react-redux
Stwórz magazyn danych w pliku src/app/store.ts. Zaczyna się on od pustego mapy reduktorów i eksportuje dwa typy pochodzące z magazynu, aby reszta aplikacji nie musiała ich ręcznie definiować:
import { configureStore } from '@reduxjs/toolkit'
export const store = configureStore({
reducer: {},
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
Obiekt reducer: {} na razie pozostaje pusty. Dodawane są tylko fragmenty danych, gdy pojawią się rzeczywiste funkcje, takie jak projekty czy zadania; nie ma sensu tworzyć stanu przed tym, zanim jakikolwiek ekran go wykorzysta.
Następnie definiuje się typowane hooki w pliku src/app/hooks.ts. Narzędzia withTypes, dostępne w najnowszych wersjach React Redux, łączą useDispatch i useSelector z typami sklepu tylko raz, dzięki czemu komponenty uzyskują pełną inferencję typów bez konieczności adnotowania każdej wywołania:
import { useDispatch, useSelector } from 'react-redux'
import type { AppDispatch, RootState } from './store'
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
export const useAppSelector = useSelector.withTypes<RootState>()
Dostarczanie sklepu do drzewa komponentów
W tym momencie sklep już istnieje, ale React o nim nie wie. <Provider> umożliwia dostęp do niego każdemu komponentowi znajdującemu się poniżej, dlatego umieszcza się go na samym szczycie drzewa w pliku src/main.tsx:
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { Provider } from 'react-redux'
import { store } from './app/store'
import App from './App'
import './index.css'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<Provider store={store}>
<App />
</Provider>
</StrictMode>,
)
Wszystko, co jest renderowane wewnątrz <Provider>, może teraz wywoływać funkcje useAppSelector i useAppDispatch.
Weryfikacja poprawności działania
Zainstaluj aplikację i upewnij się, że strona nadal jest renderowana bez błędu "could not find react-redux context". Ten błąd pojawia się wtedy, gdy komponent używa hooków Redux poza kontekstem Provider. Przy pustym sklepie nie ma jeszcze nic więcej do sprawdzenia.
Kiedy używać Redux, a kiedy wystarczy useState
Nie wszystko powinno znajdować się w Redux – przechowywanie całego stanu w sklepie jest równie błędne jak trzymanie wszystkiego lokalnie. Praktyczna zasada ogólna:
useStatedo danych istotnych dla pojedynczego ekranu lub komponentu: czy modala jest otwarta, aktualna wartość pola wprowadzania danych, wybrana opcja w menu rozwijanym.
Jeśli jakaś informacja o stanie musiałaby być przekazywana przez kilka warstw lub kopiowana między ekranami, to jest to dobry znak, że powinna znajdować się w sklepie danych (store).
React Router: routowanie przed pierwszą rzeczywistą stroną
Pakiet: react-router
Dodawanie mechanizmu routowania przed utworzeniem jakiejkolwiek rzeczywistej strony może wydawać się przedwczesne, ale szybko przynosi korzyści: każdy nowy ekran staje się dodatkowym elementem <Route>, zamiast wymagać późniejszej rekonstrukcji aplikacji.
npm install react-router
Umieść tabelę tras w osobnym module, src/routes/AppRoutes.tsx. Na razie mapuje ona / na komponent zastępczy ukształtowany za pomocą narzędzi Tailwind:
import { Route, Routes } from 'react-router'
function Placeholder() {
return (
<div className="flex min-h-screen items-center justify-center">
<p className="text-slate-600">Routes coming soon</p>
</div>
)
}
export function AppRoutes() {
return (
<Routes>
<Route path="/" element={<Placeholder />} />
</Routes>
)
}
src/App.tsx następnie po prostu renderuje tę tabelę tras:
import { AppRoutes } from './routes/AppRoutes'
function App() {
return <AppRoutes />
}
export default App
Na koniec otocz aplikację tagiem <BrowserRouter> w pliku src/main.tsx, obok dostawcy Redux:
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { Provider } from 'react-redux'
import { BrowserRouter } from 'react-router'
import { store } from './app/store'
import App from './App'
import './index.css'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
</StrictMode>,
)
Wynikowa łańcuchowa struktura to main.tsx → <App /> → <AppRoutes /> → ten element <Route>, który pasuje do adresu URL. Redux i router są niezależne, więc kolejność ich umieszczania nie ma znaczenia; jedynym wymogiem jest to, aby oba otaczały element <App>.
Weryfikacja poprawności działania
Zainstaluj npm run dev i otwórz katalog /. Jeśli pojawi się tekst zastępczy, <BrowserRouter>, <Routes> oraz <Route> są poprawnie skonfigurowane.
Jest i React Testing Library: cztery zadania, jedenaście pakietów
Pakiety: jest, @testing-library/react, babel-jest oraz kilka innych
To jest etap, który zajmuje najwięcej czasu. Sami koncepcje nie są trudne, ale „dodanie testów” oznacza w praktyce instalację około jedenaście pakietów, które obejmują cztery różne funkcje, a następnie sprawdzenie, czy jeden test przejdzie przy użyciu routera. Grupowanie pakietów według zadań znacznie ułatwia zrozumienie całego procesu.
Grupa A: narzędzie do wykonywania testów i symulowany przeglądarka
npm install -D jest jest-environment-jsdom
- jest jest narzędziem do wykonywania testów. Odnajduje pliki
*.test.tsx, je wykonywa i raportuje wyniki pozytywne oraz negatywne. Nic innego w tej sekcji nie funkcjonuje bez niego. - jest-environment-jsdom jest konieczny, ponieważ Jest działa w Node, gdzie nie istnieje zmienna
document. Dostarcza symulowany DOM, dzięki czemu komponenty mają gdzie się renderować.
Grupa B: React Testing Library składa się z trzech pakietów
npm install -D @testing-library/react
npm install -D @testing-library/jest-dom
npm install -D @testing-library/user-event
To, co ludzie nazywają „React Testing Library”, to w rzeczywistości trzy biblioteki, z których każda pełni swoją rolę:
- @testing-library/react renderuje komponent na symulowanej stronie i dostarcza funkcje do wyszukiwania, takie jak
screen.getByText(...).
toBeInTheDocument(), dzięki czemu nie musisz ręcznie porównywać wyników zapytań z wartością null.Krótko mówiąc: renderuj, sprawdzaj, wchodź w interakcję. Trzy zadania, trzy pakiety – i prawie zawsze chcesz je wszystkie.
Grupa C: zestaw narzędzi Babel umożliwiający Jestowi obsługę plików TSX
Grupa ta istnieje z jednego powodu: Jest sam nie potrafi zrozumieć plików TypeScript ani JSX.
- babel-jest łączy te dwa elementy. Jest przekazuje każdy plik przez Babel przed jego wykonaniem.
- @babel/preset-typescript usuwa adnotacje typów. Nie sprawdza nic pod kątem typów; po prostu usuwa
: stringoraz podobne składnię. - @babel/preset-react kompiluje JSX w zwykłe wywołania funkcji.
- @babel/preset-env przekształca nowoczesną składnię w tę, którą obsługuje Twoja wersja Node.
W odróżnieniu od grupy B, należy je zainstalować razem w jednym poleceniu. Wszystkie te presety wymagają kompatybilnego @babel/core, a ich instalacja po częściach w projekcie, który już zawiera Jest (który ładuje własne zależności Babel), może spowodować, że npm będzie próbował pogodzić niezgodne wersje. Jednym z objawów jest błąd ERESOLVE unable to resolve dependency tree podczas kolejnej instalacji pojedynczego pakietu. Instalacja całej grupy jednocześnie pozwala npm na rozwiązanie tego problemu poprzez użycie spójnego zestawu wersji.
Kolejna, bardziej subtelna pułapka wynika z kopiowania długich poleceń z plików PDF lub stron internetowych. Tekst z formatowaniem typu soft-wrap może po wklejeniu zamienić się w rzeczywiste przerwy wierszy, przez co nazwa pakietu taka jak @babel/preset-typescript zostaje podzielona na dwie części, a shell uruchamia drugą część jako oddzielne, bezsensowne polecenie. Wyraźne kontynuacje wierszy umożliwiają umieszczenie przerw dokładnie tam, gdzie tego chcemy. Poniżej przedstawiono składnię Windows Command Prompt:
npm install -D babel-jest ^
@babel/core ^
@babel/preset-env ^
@babel/preset-react ^
@babel/preset-typescript
Symbol ^ na końcu informuje cmd.exe, że polecenie kontynuuje się na następnym wierszu. W PowerShell symbolem kontynuacji jest cudzysłów odwrócony, a w bash lub zsh to ukośnik odwrotny. Niezależnie od używanego shella, nadal jest to dokładnie jedno polecenie npm install.
Grupa D: typy tylko dla twojego edytora
npm install -D @types/jest
Ten pakiet nie ma wpływu na sposób wykonywania testów; Babel usunął już wtedy wszystkie typy. Istnieje po to, aby TypeScript i Twój edytor rozpoznawały globalne funkcje takie jak test(...) i expect(...), zamiast sygnalizować je jako błędy.
Dodawanie skryptów testowych
Instalacja Jest nie dostarcza polecenia npm test, więc musisz sam dodać te skrypty do pliku package.json:
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"test": "jest",
"test:watch": "jest --watch"
}
npm test uruchamia cały zestaw testów raz. npm run test:watch pozostaje w tle i uruchamia ponownie tylko te testy, które zostały zmienione w pliku, który właśnie zapisałeś; miej go otwarty w drugim terminalu podczas pracy.
Warto zapamiętać jeszcze dwa polecenia na wypadek problemów:
npx jest src/App.test.tsx # run one file only
npx jest --clearCache # when Jest keeps showing an error
# you already fixed
Polecenie cache ma większe znaczenie, niż się wydaje. Jest przechowuje przetworzone pliki, więc po zmianie pliku babel.config.cjs lub jest.config.cjs może nadal serwować stary wynik i zgłaszać błąd, który już naprawiłeś. Gdy wydaje się, że naprawa nie działa, usuń cache, zanim doszedniesz do wniosku, że naprawa jest błędna.
Pełna konfiguracja testów
Poniżej znajdują się wszystkie pliki konfiguracyjne w całości, wraz z wyjaśnieniem tego, za co jest odpowiedzialny każdy z nich.
babel.config.cjs
Ustawienia domyślne są identyczne z grupą C: celują w aktualną wersję Node, wykorzystują automatyczny silnik JSX, dzięki czemu pliki nie muszą importować React, oraz usuwają TypeScript. Wbudowany plugin radzi sobie z czymś, czego nie potrafi Jest: import.meta, który jest używany w kodzie Vite do celów takich jak import.meta.env i gorąca zamiana modułów, ale który nie jest ważny w formacie CommonJS używanym przez Jest.
function stripImportMeta() {
return {
visitor: {
MetaProperty(path) {
path.replaceWithSourceString('({ url: "", hot: undefined })')
},
},
}
}
module.exports = {
presets: [
['@babel/preset-env', { targets: { node: 'current' } }],
['@babel/preset-react', { runtime: 'automatic' }],
'@babel/preset-typescript',
],
plugins: [stripImportMeta],
}
To jest prawdziwy plugin Babel, napisany jako funkcja wewnątrz kodu zamiast jako zainstalowany pakiet; Babel akceptuje obie formy. MetaProperty to typ węzła AST używany przez Babel dla import.meta, a mechanizm przeglądania zastępuje każdą jego wystąpienie zwykłym obiektem, który ma pusty polu url oraz niezdefiniowane pole hot. Należy pamiętać, że to również ukrywa wszystkie wartości z import.meta.env przed testowanym kodem, więc komponenty, które odczytują zmienne środowiskowe, będą wymagać ich symulacji osobno.
jest.config.cjs
To plik łączący narzędzie uruchamiania z całym resztą. Wybiera środowisko jsdom, ładuje plik konfiguracyjny po przygotowaniu środowiska, przekazuje każdy plik JavaScript i TypeScript przez babel-jest, a także mapuje importy stylów i obrazów na moduły zastępcze.
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'mjs', 'json'],
transform: {
'^.+\\.(ts|tsx|js|jsx|mjs)