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-dom

Instalacja

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 Contact

Komponent 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-dev

Lista 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