Lekcja 4: Komponenty Funkcyjne i Props
Komponenty to serce aplikacji React. Pozwalają dzielić interfejs użytkownika na niezależne, reużywalne części. W nowoczesnym Reactcie dominują komponenty funkcyjne, które są prostsze i łatwiejsze w użyciu dzięki Hookom (o których w kolejnych lekcjach). W tej lekcji skupimy się na tworzeniu komponentów funkcyjnych i przekazywaniu do nich danych za pomocą "props".
Komponenty Funkcyjne
Komponent funkcyjny w React to zwykła funkcja JavaScript, która przyjmuje jeden argument (obiekt "props") i zwraca elementy React (zazwyczaj JSX), opisujące, co powinno zostać wyrenderowane.
// Najprostszy komponent funkcyjny
function Welcome(props) {
return <h1>Witaj, {props.name}!</h1>;
}
// Użycie komponentu
const element = <Welcome name="Sara" />;
// Renderowanie (zakładając istnienie elementu #root)
const root = ReactDOM.createRoot(document.getElementById(\'root\'));
root.render(element);
Kluczowe cechy komponentów funkcyjnych:
- Są to standardowe funkcje JavaScript (mogą być zdefiniowane za pomocą
function
lub jako funkcje strzałkoweconst Welcome = (props) => { ... }
). - Nazwy komponentów muszą zaczynać się wielką literą (np.
Welcome
, a niewelcome
). React traktuje komponenty zaczynające się małą literą jak zwykłe tagi HTML. - Przyjmują jeden argument - obiekt
props
(skrót od properties). - Zwracają opis UI (najczęściej JSX).
Props (Properties)
Props to sposób przekazywania danych z komponentu nadrzędnego (rodzica) do komponentu podrzędnego (dziecka). Działają podobnie do atrybutów w HTML.
W przykładzie powyżej, komponent Welcome
został użyty z "propsem" name
o wartości "Sara": <Welcome name="Sara" />
. Wewnątrz komponentu Welcome
, ta wartość jest dostępna jako props.name
.
Props są tylko do odczytu (read-only). Komponent nigdy nie powinien modyfikować swoich własnych propsów. Wszystkie komponenty React muszą działać jak "czyste funkcje" w odniesieniu do swoich propsów - dla tych samych danych wejściowych (props) zawsze powinny zwracać ten sam wynik.
// Przykład przekazywania wielu propsów
function UserCard(props) {
return (
<div className="user-card">
<img src={props.avatarUrl} alt={props.name} />
<h2>{props.name}</h2>
<p>Email: {props.email}</p>
</div>
);
}
// Użycie komponentu z wieloma propsami
const element = (
<UserCard
name="Jan Kowalski"
email="jan.kowalski@example.com"
avatarUrl="https://via.placeholder.com/100"
/>
);
Możemy również użyć destrukturyzacji ES6, aby kod był bardziej czytelny:
function UserCard({ name, email, avatarUrl }) { // Destrukturyzacja props
return (
<div className="user-card">
<img src={avatarUrl} alt={name} />
<h2>{name}</h2>
<p>Email: {email}</p>
</div>
);
}
Kompozycja Komponentów
Komponenty mogą renderować inne komponenty. To pozwala na budowanie złożonych interfejsów z mniejszych, reużywalnych części.
// Komponent Welcome
function Welcome(props) {
return <h1>Witaj, {props.name}!</h1>;
}
// Komponent App, który używa komponentu Welcome wielokrotnie
function App() {
return (
<div>
<Welcome name="Sara" />
<Welcome name="Kasia" />
<Welcome name="Tomek" />
</div>
);
}
// Renderowanie komponentu App
const root = ReactDOM.createRoot(document.getElementById(\'root\'));
root.render(<App />);
Props `children`
Specjalnym propsem jest children
. Reprezentuje on zawartość umieszczoną pomiędzy tagiem otwierającym a zamykającym komponentu.
// Komponent Card, który może renderować dowolną zawartość wewnątrz
function Card(props) {
return (
<div className="card">
{props.children}
</div>
);
}
// Użycie komponentu Card
function App() {
return (
<Card>
{/* Ta zawartość będzie dostępna jako props.children w komponencie Card */}
<h2>Tytuł karty</h2>
<p>To jest treść karty.</p>
<button>Kliknij mnie</button>
</Card>
);
}
Możemy również użyć destrukturyzacji dla children
:
function Card({ children }) {
return (
<div className="card">
{children}
</div>
);
}
Domyślne wartości Props (Default Props)
Możemy zdefiniować domyślne wartości dla propsów, które będą używane, jeśli komponent nadrzędny nie przekaże danej właściwości.
function Button({ label = "Kliknij", color = "blue" }) {
const style = { backgroundColor: color, color: \'white\', padding: \'10px\', border: \'none\' };
return <button style={style}>{label}</button>;
}
// Użycie:
<Button /> // Użyje domyślnych wartości: label="Kliknij", color="blue"
<Button label="Wyślij" /> // Użyje label="Wyślij", color="blue"
<Button color="green" /> // Użyje label="Kliknij", color="green"
<Button label="Anuluj" color="red" /> // Użyje podanych wartości
Ćwiczenie praktyczne
Pokaż rozwiązanie
// src/UserInfo.jsx
import React from \'react\';
function UserInfo({ user }) {
return (
<div className="UserInfo">
<img className="Avatar" src={user.avatarUrl} alt={user.name} />
<div className="UserInfo-name">{user.name}</div>
</div>
);
}
export default UserInfo;
// src/Comment.jsx
import React from \'react\';
import UserInfo from \'./UserInfo\'; // Załóżmy, że UserInfo jest w tym samym folderze
function formatDate(date) {
return date.toLocaleDateString();
}
function Comment({ author, text, date }) {
return (
<div className="Comment">
<UserInfo user={author} />
<div className="Comment-text">{text}</div>
<div className="Comment-date">{formatDate(date)}</div>
</div>
);
}
export default Comment;
// src/App.jsx
import React from \'react\';
import Comment from \'./Comment\';
import \'./App.css\'; // Załóżmy, że mamy podstawowe style
const commentData = {
author: {
name: \'Anna Nowak\',
avatarUrl: \'https://via.placeholder.com/50\'
},
text: \'Mam nadzieję, że podoba Ci się nauka Reacta!\',
date: new Date()
};
function App() {
return (
<div>
<h1>Komentarze:</h1>
<Comment
author={commentData.author}
text={commentData.text}
date={commentData.date}
/>
</div>
);
}
export default App;
Dodaj podstawowe style w App.css
, aby komentarz wyglądał sensownie.
Cel: Stworzyć komponent Comment
, który wyświetla komentarz użytkownika (awatar, imię, treść, data).
Kroki:
- W projekcie React (np. Vite) stwórz nowy plik komponentu, np.
src/Comment.jsx
. - Zdefiniuj komponent funkcyjny
Comment
, który przyjmuje propsy:author
(obiekt zname
iavatarUrl
),text
idate
. - Użyj JSX, aby wyrenderować strukturę komentarza, wykorzystując przekazane propsy.
- Stwórz pomocniczy komponent
UserInfo
, który przyjmujeuser
(obiekt zname
iavatarUrl
) jako props i renderuje awatar oraz imię użytkownika. Użyj tego komponentu wewnątrzComment
. - W komponencie
App.jsx
zaimportuj i użyj komponentuComment
, przekazując przykładowe dane.
Zadanie do samodzielnego wykonania
Stwórz komponent Product
, który przyjmuje propsy: name
(string), price
(number), isOnSale
(boolean), features
(tablica stringów). Komponent powinien:
- Wyświetlać nazwę produktu jako nagłówek
<h3>
. - Wyświetlać cenę. Jeśli
isOnSale
jesttrue
, cena powinna być wyświetlona na czerwono i z dopiskiem "(Promocja!)". - Wyświetlać listę cech (
features
) jako<ul>
. - Użyj domyślnej wartości dla
isOnSale
nafalse
. - Użyj tego komponentu w
App.jsx
kilka razy z różnymi danymi.
FAQ - Komponenty Funkcyjne i Props
Czym różni się komponent funkcyjny od komponentu klasowego?
Komponenty funkcyjne to prostsze funkcje JavaScript, które przyjmują props i zwracają JSX. Komponenty klasowe (starsze podejście) to klasy ES6 dziedziczące po `React.Component`, mające dostęp do stanu przez `this.state` i metod cyklu życia. Od wprowadzenia Hooków, komponenty funkcyjne mogą zarządzać stanem i efektami ubocznymi, stając się preferowanym podejściem.
Dlaczego nazwy komponentów muszą zaczynać się wielką literą?
React używa tej konwencji, aby odróżnić tagi komponentów React od zwykłych tagów HTML. Jeśli nazwa zaczyna się małą literą (np. `<button />`), React traktuje ją jako tag HTML. Jeśli zaczyna się wielką literą (np. `<Button />`), React wie, że ma do czynienia z komponentem zdefiniowanym przez użytkownika.
Czy propsy mogą być czymś innym niż stringi?
Tak, propsy mogą przyjmować dowolny typ danych JavaScript: stringi, liczby, booleany, tablice, obiekty, funkcje, a nawet inne elementy React. Wartości inne niż stringi przekazujemy, umieszczając je w nawiasach klamrowych: `<MyComponent count={10} isActive={true} data={{ key: \'value\' }} onClick={handleClick} />`.
Co to znaczy, że propsy są "read-only"?
Oznacza to, że komponent nie powinien nigdy próbować zmieniać wartości propsów, które otrzymał od rodzica. Propsy służą do przekazywania danych w dół drzewa komponentów. Jeśli komponent potrzebuje zmieniać dane, powinien używać stanu (state), o którym będzie mowa w kolejnej lekcji.
Jak przekazać funkcję jako props?
Funkcję przekazuje się tak samo jak inne wartości JavaScript - w nawiasach klamrowych. Jest to powszechny wzorzec w React, np. do obsługi zdarzeń w komponencie dziecku, które wywołują funkcję zdefiniowaną w rodzicu: `<ChildComponent onAction={handleAction} />`.
Czy mogę używać destrukturyzacji props w komponencie klasowym?
W komponencie klasowym dostęp do propsów uzyskujemy przez `this.props`. Można użyć destrukturyzacji wewnątrz metod komponentu, np. w metodzie `render()`: `const { name, email } = this.props; return (<div>{name}</div>);`. Nie można jednak destrukturyzować bezpośrednio w definicji klasy jak w funkcji.
Do czego służy `props.children`?
`props.children` zawiera wszystko, co zostało umieszczone pomiędzy tagiem otwierającym a zamykającym komponentu podczas jego użycia. Pozwala to tworzyć generyczne komponenty-kontenery (np. karty, modale), które mogą opakowywać dowolną inną zawartość JSX przekazaną przez rodzica.