React zaawansowane zagadnienia
wersja: 18.x.x
(listopad 2022)
Plan
- React Router
- React Context
- prop-types
- HOC
- Testowanie komponentów Jest
React Router
v.6.1.1
React Router to zestaw komponentów, które umożliwiają nawigację w aplikacji, czyli możemy tworzyć podstrony.

BrowserRouter vs HashRouter
Router to główny komponent, dzięki któremu możemy korzystać z dobrodziejstw routingu.
W przypadku projektów internetowych, react-router-dom udostępnia routery <BrowserRouter> i <HashRouter>. Główna różnica między nimi polega na sposobie przechowywania adresu URL i komunikacji z serwerem.
BrowserRouter
Używa zwykłych ścieżek URL. np. www.nis.pl/about, ale wymagają one prawidłowego skonfigurowania serwera.
Aplikacja Create React App ma od razu przy instalacji skonfigurowane środowisko do użycia tego routera. Zawiera też instrukcje dotyczące konfiguracji serwera produkcyjnego.
HashRouter
Przechowuje bieżącą lokalizację w części za # np. www.nis.pl/#/about.
Ten rodzaj nie wymaga konfiguracji serwera.
React Router
npm install react-router-domInstalacja
React Router
Komponenty
Home
About
Contact
Navigation.js
Place for clicked item in navigation
Default page is HOME
Home.js
App.js
Stwórzmy najpierw komponenty odpowiedzialne za wyświetlanie poszczególnych podstron
//Home.js
const Home = () => <div>Home</div>
export default Home;//About.js
const About = () => <div>About</div>
export default About//Contact.js
import React from 'react'
const Contact = () => <div>Contact</div>
export default ContactKomponent Link
<Link to="/about">About</Link>Komponent Link jest odpowiedzialny za przejście do innej podstrony.
Należy przekazać props "to", w którym będzie adres podstrony.
Komponent Link jest renderowany oczywiście do tagu <a>
Stwórzmy komponent z nawigacją
//Navigation.js
import React from 'react';
import { Link } from 'react-router-dom';
const Navigation = () => {
return <ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/contact">Contact</Link></li>
</ul>
}
export default Navigation;Dostosowujemy komponent App do tego, aby wykorzystywał routing. Na początek importy
//Plik App.js
import React from "react";
import {
BrowserRouter as Router,
Routes,
Route
} from "react-router-dom";
import Home from './components/router/Home';
import About from './components/router/About';
import Contact from './components/router/Contact';
import Navigation from './components/router/Navigation';
//..... dalszy ciąg za chwilęTworzymy alias do BrowserRouter. Będzie to główny komponent aplikacji wykorzystującej Routing
Za chwile o nich więcej
W App wykorzystujemy dostępne komponenty z biblioteki
react-router-dom
//... dalsza część pliku App.js
const App = () => {
return <Router>
<div>
<Navigation />
<Routes>
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route exact path="/" element={<Home />} />
</Routes>
</div>
</Router>
}
export default App<Routes> pozwala przejrzeć dostępne możliwości podstron i wyrenderować pierwszy, który będzie pasował do bieżącego adresu URL
<Route> pozwala dopasować ścieżkę do komponentu
Ważne!
Ważną rzeczą do zapamiętania jest to, że <Route path> pasuje do początku adresu URL, a nie do całości.
Dlatego <Route path = "/"> będzie pasować do każdego adresu URL.
Z tego powodu zazwyczaj umieszczamy ten <Route> jako ostatni w <Routes> lub korzystamy z właściwości exact, która dopasowuje dokładnie taką ścieżkę.
404
Jeśli dany link już nie istnieje lub ktoś wpisał go błędnie wtedy otrzymujemy zazwyczaj błąd 404, czyli Not Found (nie znaleziono).
Spróbuj w naszej aplikacji wpisać adres:
http://localhost:3000/blog
Póki co nic się nie stanie lub zobaczysz pustą stronę.
Jak widzisz – React Router nie umie odnaleźć ścieżki.
Podstrona 404
Możemy stworzyć własną podstronę, która będzie informowała o tym, że dana ścieżka jest niepoprawna.
Stworzymy do tego dodatkowy komponent – np. NotFound.
const NotFound = () => <div>NotFound</div>
export default NotFound;<Route path="*" element={<NotFound />} />Zmodyfikujemy teraz routing w następujący sposób:
Parametry
Jedną z najważniejszych rzeczy w routingu jest możliwość przekazywania parametrów w URL.
Możemy je porównać do zmiennych, które w przeciwieństwie do pozostałej części adresu mogą się po prostu zmieniać.
Spójrzmy na stronę:
https://twitter.com/kowalski_it oraz na https://twitter.com/thecodinglove
Jeśli zmieniamy użytkownika na inną osobę zauważymy, że komponent jest ten sam, zmieniają się tylko dynamicznie dane.
Przyjmowanie parametrów
Jeśli chcemy przyjąć parametr, wystarczy część ścieżki poprzedzić znakiem dwukropka ":".
Przykład:
<Route path="/" element={ <UserInfo />} />
<Route path="user" element={ <UserDetails />} >
<Route path=":invoiceId" element={<UserDetails />} />
</Route>Za każdym razem kiedy użytkownik wpisze adres pasujący do danego wzorca (/kowal, /johnsnow, /abc itp) , zostanie załadowany komponent UserInfo.
Odbieranie parametrów
Aby odebrać parametr w danym komponencie wykorzystamy Hook useParams()
const UserDetails = () => {
let {name} = useParams();
return (
<div>
<h3>User {name}</h3>
</div>
);
}Nesting
(Zagnieżdżanie elementów)
Nesting
<Route path='/'>
<Home /> // || null
</Route>Pamiętamy, że każdy element Route dopasowuje ścieżkę do adresu url i w zależności od tego czy pasuje ona do wzorca renderuje element bądź null
Nesting
Wyobraźmy sobie taką sytuację:
Home
Blog
Navigation.js
Movies
Songs
Series
Path: blog/movies
Something about Movies
Blog
Blog.js
Topic.js
App.js
Nesting
W komponencie App nie zmieniamy za wiele dodajemy po prostu komponent Blog wskazując na jego ścieżkę w routingu
<Routes>
<Route path="/" element={<Home />}>
<Route path="blog" element={<Blog />}>
<Route path="movies" element={<Movies />} />
<Route path="songs" element={<Songs />} />
<Route path="series" element={<Series />} />
</Route>
</Routes>Context API

Context API pozwala nam stworzyć wspólny stan dla określonej liczby komponentów. Oczywiście Context może też obejmować całą aplikację.
Na Context składają się dwie cześci: ContextProvider i ContextConsumer
Context API

ContextProvider – okala komponenty w których kontekst ma być dostępny.
const ThemeContext = React.createContext(themes.light);
function App() {
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
Zawartość zmiennej themes
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};Context API

ContextConsumer – konsument kontekstu
import {ThemeContext} from './theme-context';
function ThemeTogglerButton() {
return (
<ThemeContext.Consumer>
{({theme}) => (
<button
style={{backgroundColor: theme.dark.background}}>
Przełącz motyw
</button>
)}
</ThemeContext.Consumer>
);
}
export default ThemeTogglerButton;Context API

Zapis z poprzedniego slajdu nie jest niestety wygodny – z pomoca przychodzą nam jednak hooki.
import {ThemeContext} from './theme-context';
function ThemeTogglerButton() {
const context = useContext(ThemeContext);
return (
<button
style={{backgroundColor: context.theme.dark.background}}>
Przełącz motyw
</button>
);
}
export default ThemeTogglerButton;useReducer
Hook useReducer jest bardzo podobny do hooka useState. Można powiedzieć, że jest jego znacznie rozbudowaną wersją. W pewnym sensie przypomina też funkcję reducera znane z biblioteki Redux.


useReducer – reducer
Pierwszy argument dla hooka to reducer czyli funkcja która w zależności od podjętej akcji przeprowadza odpowiednią mutację stanu.


useReducer – reducer
Drugi to initialState – czyli stan początkowy. Możesz teraz zmienić stan w nieco bardziej deklaratywny sposób.


useReducer – reducer
Hook'a useReducer warto używać w sytuacjach kiedy nasz stan jest skomplikowany lub gdy jego przekształcenia są skomplikowane i uzależnione od wielu czynników.

Prop-types
PropTypes jest biblioteką, która pozwala na sprawdzanie typów w aplikacji. Jeśli chcemy używać jej w naszym repozytorium musimy zainstalować bibliotekę prop-types za pomocą managera pakietów npm.
Używamy jej żeby kontrolować typy propsów w komponentach.
npm install prop-types --save-devLista dostępnych typów
- PropTypes.array
- PropTypes.object
- PropTypes.bool
- PropTypes.func
- PropTypes .number
- PropTypes.string
- PropTypes.symbol
Ponadto jeśli chcemy mieć pewność, że została dostarczona wartość, możemy dodać wywołanie .isRequired na końcu każdej z wymienionych na poprzednim slajdzie opcji.
App.propTypes = {
name: PropTypes.string.isRequired
}Jeżeli nie podamy wymaganej wartości dla danego propsa w konsoli zostanie wygenerowany komunikat błędu.
index.js:1 Warnin: Filed prop type:
The prop name is marked as required
in App, but its value is undefined.Jeżeli typ propsa nie ma znaczenie, ważne jest, żeby props był przekazany to możemy sprawdzić to za pomocą any.
App.propTypes = {
name: PropTypes.any.isRequired
}Jeżeli zależy nam na konkretnej wartości propsa możemy to z walidować za pomocą opcji oneOf. Jeśli zostanie przekazana opcja inna niż zawarta w tablicy, zostanie wygenerowany komunikat ostrzeżenia.
App.propTypes = {
name: PropTypes.oneOf(
["Rambo", "Terminator"])
}Przykład użycia
import PropTypes from "prop-types";
const User = ({name, surname, age}) => {
return (
<>
<p>{name}</p>
<p>{surname}</p>
<p>{age}</p>
</>
)
}
User.propTypes - {
name: PropTypes.string,
surname: PropTypes.string,
age: PropTypes.number
}Higher-Order Components
Komponenty wyższego rzędu (HOC) to technika dzięki, której możemy wielokrotnie wykorzystywać logikę komponentu.
HOC to funkcja/component, który jako parametr przyjmuje komponent i zwraca nowy komponent
const newComponent = hoc(WrappedComponent);Higher-Order Components
Korzystając z porównania możemy powiedzieć że:
const ironMan = withSuit(TonyStark);Higher-Order Components
const updatedComponent = OriginalComponent => {
const NewComponent = (props) => {
return <OriginalComponent {...props}/>
}
return NewComponent
}Podstawowy szablon HOC
Tutaj funkcja przyjmuje komponent OriginalComponent jako parametr i zwraca go w niezmienionym stanie
Wprowadzenie do testowania aplikacji React
Testowanie
Zalet samego testowania jest mnóstwo np.: eliminacja częściowa regresji, łatwiejsze dodawanie nowych funkcji, testy służące jako dokumentacja itp.
Innymi słowy testowanie sprawia, że aplikacja jest mniej podatna na błędy. Sprawdzamy czy nasz kod robi to, co chcemy, i czy aplikacja działa zgodnie z przeznaczeniem.
Unit testy
Tego typu testy sprawdzają poszczególne fragmenty lub komponenty oprogramowania.
Fragmentem może być funkcja, metoda, procedura, moduł lub obiekt.

Testowanie modułowe
Testowanie modułowe sprawdza funkcjonalność poszczególnych części aplikacji.
Testy są wykonywane na każdym komponencie w oderwaniu od innych.
Aplikacje React składają się z wielu komponentów, tak więc testowanie komponentów polega na testowaniu ich indywidualnie.
TDD
Najpierw piszesz test potem implementację.
Jest
Jest to lekki i prosty framework służący do testowania języka JavaScript. Zawiera test runner na bazie Node, tzn. że nie potrzebujemy przeglądarki żeby uruchamiać kod.
Aby wygodnie pisać testy potrzebujemy:
- środowiska uruchomieniowego (test runnera)
- test frameworka - czyli frameworka, który zadba o to jak od strony kodu będą wyglądały nasze testy
- narzędzia do asercji - czyli walidowania efektów np. wywołania funkcji.

React testing library
React Testing Library jest zestawem funkcji pomocniczych, które pozwalają nad testowanie komponentów reactowych bez polegania na ich szczegółach implementacyjnych (ang. implementation details). Doskonale sprawdza się w połączeniu z Jestem i jego funkcjonalnością mockowania modułów.
Instalacja i konfiguracja
Dobra wiadomość jest taka, że Jest i React Testing Library są zainstalowane, skonfigurowane i gotowe do pracy w aplikacji create-reacta-app.
Pierwszy test w pliku multiplyByTwo.test.js
//Zaimportowana funkcja
function multiplyByTwo = (x) => {
return x*2;
}
test("Mnożenie przez 2", () => {
expect(multiplyByTwo(2).toBe(9))
});expect
Podczas pisania testów sprawdzamy czy wartości spełniają określone warunki. Funkcja expect daje dostęp do szeregu „dopasowań”, które pozwalają zweryfikować wiele rzeczy.
expect(100).toBeWithinRange(90, 110);
expect(2===2).toEqual(true);Więcej: https://jestjs.io/docs/en/expect#expectvalue
expect().toBe()Konwencja nazewnicza
- Pliki z rozszerzeniem .js w folderze __tests__
- Pliki z rozszerzeniem .test.js
- Pliki z rozszerzeniem .spec.js
Jest będzie automatycznie uruchamiał testy spełniające konwencję nazewniczą.
Renderowanie i sprawdzanie komponentów React
import { render, screen } from '@testing-library/react';
import Home from './Home';
test('renders Home', () => {
render(<Home />);
const el = screen.getByText('To jest komponent home.');
expect(el).toBeInTheDocument();
});Dokumentacja
Copy of deck
By noinputsignal
Copy of deck
- 5