(czerwiec 2025)
Część 1
React jest JavaScriptową biblioteką
stworzoną przez Facebooka.
Służy do tworzenia frontendowych interfejsów użytkownika.
Duże, aktywne środowisko – łatwo znaleźć wsparcie
Ma wsparcie dużej firmy Facebook
Pozwala na dzielenie większej aplikacji na mniejsze fragmenty, dzięki własnym komponentom.
Dzięki takim rozwiązaniom jak np. wirtualny DOM,
jest bardzo wydajną biblioteką.
"In the essence framework is solving structural and architectural problems on the code level."
"React does not solve any structural or architectural problems on the app level. It provides us with a set of methods for better handling of front-end."
Manipulacja elementami DOM za pomocą JS jest bardzo wolna i nie wydajna. Jednym z głównych powodów jest fakt, że aktualizujemy DOM w zakresie o wiele większym, niż niezbędny.
Wirtualny DOM (VDOM) to reprezentacja drzewa DOM przechowywana w pamięci i synchronizowana z nim za pomocą biblioteki ReactDOM. Proces ten nosi nazwę reconciliation.
UWAGA: Shadow DOM to nie to samo co Virtual DOM
porozmawiamy o tym szerzej trochę później ;)
To rozszerzenie języka JavaScript o tagi podobne do tych, które stosujemy w HTML
const element = <h1>Hello, world!</h1>;Za chwilę porozmawiamy o tym szerzej :)
Przykład:
1. Edytor: https://www.jetbrains.com/webstorm/
2. Node: https://nodejs.org/en/
3. Chrome: https://www.google.pl/chrome/browser/desktop/index.html
4. Terminal/Git bash: https://gitforwindows.org/
https://marketplace.visualstudio.com/items?itemName=jawandarajbir.react-vscode-extension-pack
Vite.js to narzędzie do budowania projektów, które zostało stworzone w celu zwiększenia szybkości i wydajności procesu programistycznego. Zalety:
npm init vite@latest my-project
Główne 2 metody, dostarczane przez moduł react-dom to:
const root = ReactDOM.createRoot(element do
którego będziemy wstrzykiwać aplikację);
root.render(element, który wstrzykujemy)Renderujemy (wstawiamy do rzeczywistego drzewa DOM) elementy, które chcemy wyświetlić.
<br /> lub <image src="src/logo.png" /><div style={{color: 'red' }}/>
<div height="0"/>JSX
<p id="logo">Hello, World</p>JS
React.createElement(
"p",
{id: "logo"},
"Hello, World"
);Wyrażeniem JSX jest dowolny kod JavaScript, który coś zwraca np.:
<div>{ 4 + 3 }</div>const name = 'Jan';
const count = name.length;
<div>Twoje imię ma { count } znaków</div>const name = 'Jan';
const str = <p>Hello,{ name }</p>
<div>{ str }</div>const name = 'Jan';
const cmp = <Hello>Hello,{ name }</Hello>
<div>{ cmp }</div>Jeszcze nie wiemy co to komponenty, ale wkrótce się wszystko wyjaśni.
Wyrażenia
<div contentEditable={ true }>Sample<∕div>const style = {
color: 'blue',
backgroundColor: 'green'
};
<div style={style}><∕div>Atrybuty
return <ul>
<li key="1">Element 1</li>
<li key="1">Element 2</li>
<li key="3">Element 3</li>
</ul>;
return [
<li key="1">Element 1</li>,
<li key="1">Element 2</li>,
<li key="3">Element 3</li>,
];
React 15.x - element główny
React 18.x - tablica elementów
return <React.Fragment>
<li key="1">Element 1</li>,
<li key="1">Element 2</li>,
<li key="3">Element 3</li>,
</React.Fragment>;
return <>
<li key="1">Element 1</li>
<li key="1">Element 2</li>
<li key="3">Element 3</li>
</>;
React 18.x - Fragment
React 18.x - Fragment-
skrócona wersja
const tab = [1,2,3];
return <ul>
{
tab.map((el, i) => <li key={i}>{el+2}</li>)
}
</ul>;
// <li key="1">3</li>,
// <li key="1">4</li>,
// <li key="3">5</li>,
Elementy JSX możemy wyrenderować z tablicy poprawnych elementów JSX lub komponentów.
Kiedy zwracamy kolekcję musimy każdemu elementowi nadawać unikalny klucz (key). React wykorzystuje klucze w celach optymalizacyjnych.
Zagnieżdżanie
Wydajność algorytmów Reacta wynika z "wiedzy" w jaki sposób najczęściej postępują programiści (algorytmy heurystyczne)
Poznajmy kilka zasad zgodnie z którymi React dokonuje zmian w DOM.
Powinniśmy wskazywać, które elementy są stabilne pomiędzy zmianami za pomocą kluczy.
<ul>
<li> Jan </li>
<li> Ala </li>
</ul>
//Dodajemy element na koniec
//Nie ma problemu
<ul>
<li> Jan </li>
<li> Ala </li>
<li> Ela </li>
</ul>Dlaczego?
<ul>
<li> Jan </li>
<li> Ala </li>
</ul>
//Dodajemy element na początek
//Jest problem
<ul>
<li> Ola </li>
<li> Jan </li>
<li> Ala </li>
</ul><ul>
<li key="89237284"> Jan </li>
<li key="81928284"> Ala </li>
</ul>
//Dodajemy element na początek
//Jest problem
<ul>
<li key="77284737"> Ola </li>
<li key="89237284"> Jan </li>
<li key="81928284"> Ala </li>
</ul>Klucze pomagają Reactowi zidentyfikować, które elementy uległy zmianie, zostały dodane lub usunięte.
const App = () => {
const animals = ["dog", "cat", "horse"];
const liElements = animals.map( el => {
return <li> {el} </li>
})
return liElements;
}Jeśli generujemy elementy w tablicy, React ostrzega nas w konsoli o problemie.
const App = () => {
const animals = ["dog", "cat", "horse"];
const liElements = animals.map( (el,i) => {
return <li key={i}> {el} </li>
})
return <ul>{liElements}</ul>
}musimy dodać klucze do każdego elementu listy .
Moglibyśmy wykorzystać index np.:
ale ...
React odradza używania zwykłych indeksów jako kluczy.
Jeśli klucz jest indeksem, zmiana kolejności elementu zmienia ten indeks - co powoduje, że nie jest on stabilny/tożsamy z danym elementem!
Może to powodować problemy podczas działania aplikacji np. usuwanie nie tego elementu co trzeba.
Najlepiej skorzystać z id danego elementu.
<div>
<h1> Hello </h1>
<Counter />
</div>
//Jeżeli zmienimy element główny to
//wszystko jest montowane od nowa
<section>
<h1> Hello </h1>
<Counter />
</section>Jeżeli zmieni się element główny to jego dzieci również się zmieniają
Możemy wskazać, które elementy są stabilne pomiędzy zmianami dzięki atrybutowi key w listach.
Komponent to pojedynczy element interfejsu użytkownika, odpowiedzialny za konkretną część aplikacji. Składa się z elementów i/lub innych komponentów.
Komponent jest funkcją.
const Hello = () => {
return <h1>Hello World </h1>
}
//Wywołujemy
<Hello />class Hello extends React.Component {
render() {
return <h1> Hello </h1>
}
}
//Wywołujemy
<Hello />Zrób zadania z sekcji Komponenty -> Podstawy
props to sposób na przekazywanie informacji komponentom tak samo jak atrybuty przekazują je elementom.
<Hi name="Zygmunt" />
//Zapis funkcyjny
const Hi = (props) => <h1>Hi, {props.name}</h1>
//Zapis klasowy
class Hi extends React.Component {
render() {
return <h1>Hi, {this.props.name}</h1>;
}
}<Hi numbers={[1,2,3]} />
//Zapis funkcyjny
const Hi = (props) => <div> Hi,
{props.numbers.map( e => <span> {e} </span> )}
</div>
//Zapis klasowy
class Hi extends React.Component {
render() {
return <div> Hi,
{this.props.numbers.map( e => <span> {e} </span> )}
</div>
}
}Zrób zadania z sekcji Komponenty -> props
const pureFunc = (a, b) => {
return a + b;
}
pureFunc(2,2) // 4;
pureFunc(2,3) // 5
const dirtyFunc = () => {
return Math.random();
}
dirtyFunc() //0.5211613377746926
dirtyFunc() //0.4766865051280922Czym jest pure function?
W kontekście Reacta i Reduxa wszelkie komponenty i funkcje powinny być "pure".
Komponent nie powinien modyfikować danych, które otrzymuje.
class KomponentB extends React.Component {
render() {
return <div> {this.props.name} </div>
}
}
class KomponentA extends React.Component {
render() {
return <KomponentB name={this.props.name} />
}
}
ReactDOM.render(
<KomponentA name="Ala"/>,
document.getElementById("root")
)Komponenty klasowe
const KomponentB = ({name}) => <div> {name} </div>
const KomponentA = ({name}) => <KomponentB name={name} />
ReactDOM.render(
<KomponentA name="Ala"/>,
document.getElementById("root")
)Komponenty funkcyjne
Niektóre komponenty nie muszą wiedzieć niczego o
swoich dzieciach. Wystarczy, że je tylko wyrenderują
const CrazyBg = (props) => {
return (
<div style={ {backgroundColor: props.color}}>
{props.children}
</div>
);
}
const NotLogged = () => {
return (
<CrazyBg color="green">
<a href="#">Zaloguj</a>
</CrazyBg>
);
}
const Logged = (props) => {
return (
<CrazyBg color="red">
<h3>
Witaj {props.name}
</h3>
<a href="#">Wyloguj</a>
</CrazyBg>
);
}
const App = () => {
return <>
<Logged name="Agata"/>
<NotLogged />
</>
}kompozycja
State (z ang. "stan") jest obiektem przechowującym aktualny, wewnętrzny stan danego komponentu.
Stan możemy zmieniać, ale musimy poznać metody Cyklu Życia Komponentu
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
render() {
return <h1>
{this.state.counter}
</h1>
}
}Jedną z metod cyklu życia komponentu jeste metoda render(). Odpowiada ona za wyrenderowanie w drzewie DOM odpowiedniego elementu JSX.
Aby móc zarządzać elementami drzewa DOM potrzebujemy mieć pewność, że zostały już "zamontowane". Do tego użyjemy componentDidMount() i to tutaj m.in. będziemy aktualizować DOM.
// Źle
componentDidMount () {
this.state.counter += 1;
}
Nie aktualizujemy state w ten sposób!!!!
Korzystamy z setState()
//Dobrze
componentDidMount () {
this.setState({
counter: this.state.counter + 1
})
}
UWAGA: Funkcja setState() nie zmienia stanu od razu tzn. działa asynchronicznie. React grupuje kilka wywołań setState w jedno wywołanie zwiększając tym samym wydajność aplikacji.
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
componentDidMount() {
this.id = setInterval( () => {
this.setState({
counter: this.state.counter + 1
})
})
}
render() {
return <h1>
{this.state.counter}
</h1>
}
}class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
componentDidMount() {
this.id = setInterval( () => {
this.setState({
counter: this.state.counter + 1
})
})
}
componentWillUnmount() {
clearInterval(this.id);
}
render() {
return <h1>
{this.state.counter}
</h1>
}
}Dodajemy sprzątanie
Montowanie - kiedy komponent jest tworzony i wstrzykiwany do DOM
- constructor()
- render()
- componentDidMount()
Aktualizacja - kiedy komponent jest przerenderowywany
- shouldComponentUpdate()
- render()
- componentDidUpdate()
Odmontowywanie - kiedy komponent jest usuwany z DOM.
- componentWillUnmount()
State w komponentach klasowych
(Hooks)
Hooki to dodatek do Reacta 16.8. Wcześniej nie istniała taka funkcjonalność. Zamiast tego korzystaliśmy z klas
(Dzięki Hooks możemy używać state i metod cyklu życia komponentu bez użycia klas.)
Hook to funkcja, dzięki której możemy "zahaczyć" ją w wewnętrzne mechanizmy Reacta.
import React, { useState } from 'react';
const Counter2 = () => {
const [counter, setCounter] = useState(0);
return (
<div>
Hook {counter}
</div>
)
}Jako parametr przyjmuje wartość początkową dla zmiennej counter. Zwraca parę: aktualną wartość stanu oraz funkcje, którą będzie go aktualizować.
import React, { useState } from 'react';
const Counter2 = () => {
const [counter, setCounter] = useState(0);
const [name, setName] = useState("Ala");
const [animal, setAnimal] = useState("Dog");
return (
<div>
Hook {counter}
<h1>{name} {animal} </h1>
</div>
)
}Kilka wartości naraz
//Zwraca parę
const [animal, setAnimal] = useState('Reksio');
//Inaczej
var animalState = useState('Reksio'); // Zwraca parę
var animal = animalState[0]; // Pierwszy element pary
var setAnimal = animalState[1]; // Drugi element paryDzięki useEffect komponent może wykonać jakiś efekt uboczny np:
useEffect to połączenie: componentDidMount, componentDidUpdate i componentWillUnmount
import React, { useState, useEffect } from 'react';
const Counter2 = () => {
useEffect(() => {
const id = setInterval(() => {
setCounter(counter => counter+1)
}, 1000)
})
return (
<div>
Hook {counter}
</div>
)
}Tutaj useEffect jest wywoływane za każdym razem gdy zmieni się komponent.
import React, { useState, useEffect } from 'react';
const Counter2 = () => {
const [counter, setCounter] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCounter(counter => counter+1)
}, 1000)
}, [])
return (
<div>
Hook {counter}
</div>
)
}Teraz useEffect jest wywołane tylko raz, na początku.
Do useEffect przekazujemy drugi argument w postaci pustej tablicy
useEffect(() => {
const id = setInterval(() => {
console.log(counter) // zawsze 0
setCounter(counter => counter+1)
}, 1000)
}, [])
Jeśli chcemy mieć dostęp do poprzedniej wartości stanu musimy skorzystać z funkcji callback przekazanej do setCounter. Powód?
Mamy dostęp do zmiennej counter tylko w pierwszym renderowaniu, ponieważ useEffect nie jest wywoływany po raz drugi. Counter jest zawsze równy 0 przy wywoływaniu setInterval.
useEffect(() => {
const id = setInterval(() => {
setCounter(counter => counter+1)
}, 1000)
return () => clearInterval(id);
}, [])
A jeśli chcielibyśmy zaktualizować komponent tylko wtedy, gdy zmieni się jakaś konkretna wartość?
import React, { useState, useEffect } from 'react';
const App = () => {
const [title, setTitle] = useState("Empty")
useEffect(() => {
document.title = title
}, [title])
return <input
type="text"
onChange={e => setTitle(e.target.value)}
/>
}Korzystamy z tablicy przekazanej jako drugi argument.
Uruchamiamy ponownie efekt, gdy zmieni się wartość title
State w komponentach funkcyjnych
import React from 'react';
function Button({ header, name }) {
const options = {
header,
name,
};
return <FancyComponent options={options} />;
}Za każdym razem kiedy komponent Button jest tworzony nowy obiekt options. A gdyby można go było zapamiętać pomiędzy renderowaniami i aktualizować tylko wtedy, gdy zmieni się wartość header lub name?
import React from 'react';
function Button({ header, name }) {
const options = useMemo(() => ({
header,
name,
}), [header, name]) ;
return <FancyComponent options={options} />;
}Możemy do tego wykorzystać hook - useMemo(), który jako drugi parametr przyjmuje tablice zależności.
import React from 'react';
function Button({ header, name }) {
const handleClick = () => {
console.log( header,name);
}
return <FancyComponent onClick={handleClick} />;
}Kolejny przykład to kiedy renderujemy komponent i razem z nim za każdym razem tworzymy nową funkcję.
import React from 'react';
function Button({ header, name }) {
const handleClick = useCallback(() => {
console.log(header,name);
}, [header, name ])
return <FancyComponent onClick={handleClick} />;
}Możemy ją sobie zapamiętać pomiędzy renderowaniami dzięki useCallback(). W ten sposób nasza funkcja jest memoizowana (nie zostanie stworzona nowa funkcja, dopóki nie zmienią się header i name)
Jeśli chcielibyśmy stworzyć zmienną, która będzie widoczna w całym komponencie i będzie niezależna od cyklu życia komponentu możemy skorzystać z hooka useRef()
useRef() zwraca nam mutowalny obiekt (referencję) z polem current. Pole to możemy podczas tworzenia obiektu zainicjalizować.
const ref = useRef(initialValue);
useEffect(() => {
ref.current = newValue
})Do zapamiętania:
- wartość referencji stworzonej za pomocą useRef() jest stała pomiędzy kolejnymi renderowaniami komponentu
- Aktualizacja referencji nie powoduje ponownego renderowania komponentu
const App = () => {
const [counter, setCounter] = useState(0)
const timer = useRef();
useEffect(()=> {
timer.current = setInterval(()=>{
setCounter(counter => {
if(counter >=5 )
clearInterval(timer.current)
return counter + 1
})
}, 1000)
return () => clearInterval(timer.current)
}, [])
return <div> Counter: {counter} </div>
}const App = () => {
const [counter, setCounter] = useState(0)
const timer = {current: null} // za kazdym razem nowy obiekt
console.log("ja się renderuje!")
useEffect(()=> {
timer.current = setInterval(()=>{
setCounter(counter => {
if(counter >=5 )
clearInterval(timer.current)
return counter + 1
})
}, 1000)
return () => clearInterval(timer.current)
}, [])
return <div> Counter: {counter} </div>
}// ...
let timer = useRef();
useEffect(()=> {
//tak też nie. Wyrzucamy całą referencję
//zastepując ją zwykła zmienną
timer = setInterval(()=>{
setCounter(counter => {
if(counter >=5 ) clearInterval(timer)
return counter + 1
})
}, 1000)
return () => clearInterval(timer)
}, [])
// ...// timer jest obiektem, który jest
// przechowywany przez mechanizmy Reacta
const timer = useRef();
// Tutaj aktualizuje tylko
// własność tego obiektu.
timer.current = 1234; Nazwy zdarzeń w React są takie same jak w JavaScript z jedną małą różnicą, że na początku dodajemy słówko "on"
| onClick | onChange | onMouseEnter |
|---|---|---|
| onMouseLeave | onKeyPress | onKeyDown |
| onFocus | onBlur | onSubmit |
Przykładowe:
https://reactjs.org/docs/events.html
const ClickTest = () => {
const [counter, setCounter] = useState(0);
const handleClick = () => {
setCounter(counter + 1)
}
return (
<div>
<h1>{counter}</h1>
<button onClick={ handleClick }>
Kliknij mnie
</button>
</div>
)
}onClick
const ClickTest = () => {
const [counter, setCounter] = useState(0);
const handleClick = (e, name) => {
console.log(e, name)
setCounter(counter + 1)
}
return (
<div>
<h1>{counter}</h1>
<button onClick={ e => handleClick(e, "Jan")}>
Kliknij mnie
</button>
</div>
)
}
Zrób zadania z sekcji Zdarzenia -> Podstawy
const Hello = (props) => {
return <div>
Hello {props.name}
</div>
}
const Counter = (props) => {
return <div>
{props.num}
</div>
}
const App = () => (
<div>
<Hello name="Jan"/>
<Hello name="Giorgio" />
<Counter num="1" />
</div>
);App
Hello
Counter
const Hello = (props) => {
return <div>
Hello {props.name}
</div>
}
const Counter = (props) => {
const handleClick = () => {
props.add();
}
return <div> {props.num}
<button onClick={handleClick}> Add </button>
</div>
}
const App = () => {
const [counter, setCounter] = useState(0);
const addNum = () => {
setCounter(counter + 1)
}
return <div>
<Hello name="Jan" />
<Hello name="Giorgio" />
<Counter num={counter}
add={addNum}
/>
</div>
}Zrób zadania z sekcji Przekazywanie zdarzeń
const App = () => {
const [isLogged, setIsLogged] = useState(false);
if(isLogged) {
return <h1> Witaj </h1>
} else {
return <span>Zaloguj</span>
}
}const App = () => {
const [isLogged, setIsLogged] = useState(false);
const el = isLogged ?
<h1>Witaj</h1> :
<span>Zaloguj</span>
return el;
}operator trójargumentowy
const App = () => {
const [msgs, setMsgs] = useState([]);
return msgs.length > 0 &&
<h1>Masz {msgs.length}
nieprzeczytanych wiadomości
</h1>
}operator logiczny &&
const App = () => {
return null;
}Jeśli nie chcemy wyświetlić komponentu możemy zwrócić null lub po prostu false
Zrób zadania z sekcji renderowanie i blokowanie
const App = () => {
const [value, setValue] = useState("");
const handleChange = (event) => {
setValue(event.target.value)
}
return <form>
<label>
Email:
<input type="text"
value={value}
onChange={handleChange}/>
</label>
</form>
}const App = () => {
const [value, setValue] = useState("");
const handleChange = (event) => {
setValue(event.target.value)
}
const handleSubmit = () => {
//walidacja, wysyłanie
}
return <form onSubmit={handleSubmit}>
<label>
Wiadomość:
<textarea type="text"
value={value}
onChange={handleChange}/>
</label>
<input type="submit" value="wyślij" />
</form>
}<form onSubmit={handleSubmit}>
<label>
Wybierz ulubionego zwierzaka:
<select value={value}
onChange={handleChange}>
<option value="dog">Dog</option>
<option value="cat">Cat</option>
<option value="shrimp">Shrimp</option>
<option value="goose">Goose</option>
</select>
</label>
<input type="submit" value="Wybierz" />
</form>const App = () => {
//.....
return <form>
<label>
Email:
<input type="text"
value={values.email}
name="email"
onChange={handleChange}/>
</label>
<label>
Password:
<input type="password"
value={values.password}
name="password"
onChange={handleChange}/>
</label>
</form>
}Ustawiamy właściwość name
const App = () => {
const [values, setValues] = useState({email: "", password: ""});
const handleChange = (event) => {
const name = event.target.name;
setValues({
...values,
[name]: event.target.value
})
}
return //...
}useRef()
Używamy zazwyczaj do przechowywania odniesień do elementów DOM lub do przechowywania prostych typów i obiektów.
const myRef = useRef(null); <input type="text"
ref={myRef}
onChange={handleChange}/>useRef()
Pamiętaj, że odwołujemy się do elementów DOM HTML, a nie do komponentów React.
Aby dostać się do referencji wskazującej na element korzystamy z pola "current"
console.log(myRef.current)useRef()
const App = () => {
const myRef = useRef(null);
const handleChange = (event) => {
console.log(myRef.current.value)
}
return <form>
<label>
Email:
<input type="text"
ref={myRef}
onChange={handleChange}/>
</label>
</form>
}Zrób zadania z sekcji Formularze
Fetch API to narzędzie do komunikowania się z różnymi źródłami danych. Różnica między nim a XMLHttpRequest polega nam tym, że Fetch korzysta z Promises, dzięki temu możemy uniknąć tzw. callback hell.
fetch('https://api.kanye.rest/');fetch() zwraca obiekt typu Promise. Aby odebrać dane możemy użyć metody Promise.then().
fetch('https://api.kanye.rest/')
.then( resp => {
console.log( resp );
});Aby uzyskać z niego odpowiedź w formacie json musimy użyć: Response. json()
Metoda ta zwróci kolejny obiekt typu Promise, który tym razem przy spełnieniu zwróci nam dane w wersji jsona.
fetch('https://api.kanye.rest/')
.then( resp => resp.json())
.then( data => {
console.log(data)
})fetch('https://api.kanye.rest/')
.then( resp => resp.json() )
.then( data => {
console.log(data)
})
.catch( error => {
console.log(error)
})fetch(url, {
headers : {
'X-My-Header' : 'test'
}
});Jeżeli musimy przesłać nagłówek to:
fetch(url, {
method : 'POST'
});const user = {
name: "Aga"
};
fetch(url, {
method : 'POST',
body: JSON.stringify( user )
}); useEffect(() => {
fetch('https://api.kanye.rest/')
.then(r=>r.json())
.then(response => {
setData(response)
})
}, [])fetch używamy w komponentach klasowych w funkcji componentDidMount natomiast w funkcyjnych w useEffect.
Po otrzymaniu danych zmieniamy state.
Async/ Await zapewnia nam przyjemniejszą składnię w przypadku zagmatwanych scenariuszy. Dzięki Async/ Await kod wygląda na synchroniczny.
const url = 'https://api.kanye.rest/';
useEffect(() => {
(async () => {
const response = await fetch(url);
const json = await response.json();
setData(json);
})();
}, []);Zrób zadania z sekcji Fetch