Kurs JavaScript i React · Moduł 12: React i TypeScript
Typowanie Komponentów Funkcyjnych
W tej lekcji3
Zbudowałeś moduł statku, podłączyłeś go do reszty, a po tygodniu ktoś z załogi przekazuje mu liczbę tam, gdzie moduł oczekiwał tekstu. Nikt tego nie zauważył, bo komponent nie miał żadnej instrukcji obsługi. W React każdy komponent to funkcja. TypeScript pozwala nam precyzyjnie określić, co ta funkcja przyjmuje (props) i co zwraca (JSX). To jak instrukcja obsługi każdego modułu na statku kosmicznym.
React.FC vs jawne typowanie
Istnieją dwa popularne sposoby typowania komponentów funkcyjnych w React. Oba spotkasz w cudzym kodzie, więc warto rozumieć każdy z nich, zanim wybierzesz swój.
Sposób 1: React.FC (Function Component)
React.FC (skrót od React.FunctionComponent) to typ generyczny: w nawiasach ostrych podajesz typ props, a on opisuje całą funkcję komponentu. Przypisujesz go do stałej, nie do parametru. Aby użyć nazwy React.FC, trzeba zaimportować React (albo sam typ FC).
1import React from 'react';
2
3// React.FC opisuje całą funkcję komponentu (props + zwracany typ)
4const StarInfo: React.FC = () => {
5 return <p>Informacje o gwieździe</p>;
6};Bez parametru generycznego React.FC oznacza komponent bez props. Gdy props są potrzebne, najpierw opisujemy je interfejsem StarProps, a potem podajemy ten interfejs w nawiasach ostrych:
1// React.FC z propsami
2interface StarProps {
3 name: string;
4 brightness: number;
5}
6
7const StarCard: React.FC<StarProps> = ({ name, brightness }) => {
8 return (
9 <div>
10 <h3>{name}</h3>
11 <p>Jasność: {brightness}</p>
12 </div>
13 );
14};TypeScript wie teraz, że name to tekst, a brightness to liczba, choć w samej destrukturyzacji nie ma żadnych typów - przyszły z adnotacji na stałej StarCard.
Sposób 2: Jawne typowanie (zalecane)
Drugi sposób polega na otypowaniu samego parametru funkcji, dokładnie tak jak w zwykłej funkcji TypeScript. Typ zwracany możesz zapisać jako React.JSX.Element, czyli "element JSX".
1// Jawne typowanie - bardziej przejrzyste i elastyczne
2interface StarProps {
3 name: string;
4 brightness: number;
5}
6
7function StarCard({ name, brightness }: StarProps): React.JSX.Element {
8 return (
9 <div>
10 <h3>{name}</h3>
11 <p>Jasność: {brightness}</p>
12 </div>
13 );
14}Ten sam zapis działa w funkcji strzałkowej - typ props nadal stoi przy parametrze, a nie przy stałej:
1// Lub jako arrow function
2const StarCardArrow = ({ name, brightness }: StarProps): React.JSX.Element => {
3 return (
4 <div>
5 <h3>{name}</h3>
6 <p>Jasność: {brightness}</p>
7 </div>
8 );
9};JSX w środku nie zmienił się ani o znak w porównaniu ze sposobem 1. Różnica jest wyłącznie w tym, gdzie stoi informacja o typach.
Dlaczego jawne typowanie jest zalecane?
Społeczność React w dużej mierze odeszła od React.FC z kilku powodów:
- Historycznie React.FC dodawał children - do @types/react 17 każdy komponent React.FC "przyjmował"
children, nawet gdy ich nie używał. Od @types/react 18 już tak nie jest, ale wiele starych artykułów wciąż to powtarza - Jawne typowanie jest bardziej czytelne - widać dokładnie, co komponent przyjmuje
- Lepsza kompatybilność - działa identycznie z function i arrow function, a także z komponentami generycznymi, których React.FC nie obsługuje
Zobacz, jak wygląda próba użycia children w obu podejściach we współczesnym React:
1// React.FC - od @types/react 18 children NIE jest dodawane automatycznie
2const Badge: React.FC<{ label: string }> = ({ label, children }) => {
3 return <span>{label}{children}</span>; // BŁĄD: Property 'children' does not exist
4};
5
6// Jawne typowanie - children trzeba jawnie dodać
7interface BadgeProps {
8 label: string;
9}
10
11function Badge({ label }: BadgeProps): React.JSX.Element {
12 return <span>{label}</span>; // nie ma children, bo nie jest w props
13}W obu przypadkach, jeśli komponent ma przyjmować children, musisz dopisać je do typu props - zrobimy to w następnej lekcji. Moja rada: pisz jawne typy przy parametrze. Jest to krótsze, czytelniejsze i zgodne z przykładami na react.dev.
Zwracany typ komponentu
Komponent React może zwracać różne rzeczy: element JSX, tekst, liczbę albo null, gdy nie ma nic do pokazania. Każdej z tych możliwości odpowiada inny typ:
1// React.JSX.Element - najczęściej używany
2function Planet(): React.JSX.Element {
3 return <div>Mars</div>;
4}
5
6// React.ReactElement - bardziej ogólny typ elementu React
7function Planet(): React.ReactElement {
8 return <div>Mars</div>;
9}Ważna poprawka do starszych tutoriali: zapis JSX.Element bez przedrostka korzystał z globalnej przestrzeni nazw JSX, która w typach React 19 została usunięta. Pisz React.JSX.Element - działa w React 18 i 19. Jeśli komponent może zwrócić null lub tekst, użyj najszerszego typu:
1// React.ReactNode - najbardziej elastyczny (JSX, string, number, null)
2function MaybeContent(): React.ReactNode {
3 return null; // może zwrócić null
4}
5
6// W praktyce najczęściej pomijamy typ zwracany
7// TypeScript sam go wydedukuje
8function Planet() {
9 return <div>Mars</div>; // TypeScript wie, że to React.JSX.Element
10}Komponent zwracający React.ReactNode możesz używać w JSX od TypeScript 5.1. W codziennej pracy najczęściej po prostu pomijasz typ zwracany i pozwalasz kompilatorowi go wydedukować - otypowane props to ta część, która naprawdę chroni Twój statek.
Poćwicz oba style w edytorze. W kolejnej lekcji rozbudujemy interfejs props o pola opcjonalne i children.
Kod do tej lekcji: App.tsx
1import React from 'react';
2
3// Interfejs propsow
4interface CrewMemberProps {
5 name: string;
6 rank: string;
7 specialization: string;
8 yearsInSpace: number;
9}
10
11// Sposob 1: React.FC (Function Component)
12const CrewBadgeFC: React.FC<CrewMemberProps> = ({ name, rank, specialization, yearsInSpace }) => {
13 return (
14 <div className="badge-card">
15 <div className="badge-header">FC</div>
16 <h3>{name}</h3>
17 <p className="rank">{rank}</p>
18 <p>Specjalizacja: {specialization}</p>
19 <p>Lata w kosmosie: {yearsInSpace}</p>
20 </div>
21 );
22};
23
24// Sposob 2: Jawne typowanie (zalecane)
25function CrewBadgeExplicit({ name, rank, specialization, yearsInSpace }: CrewMemberProps): JSX.Element {
26 return (
27 <div className="badge-card">
28 <div className="badge-header">Explicit</div>
29 <h3>{name}</h3>
30 <p className="rank">{rank}</p>
31 <p>Specjalizacja: {specialization}</p>
32 <p>Lata w kosmosie: {yearsInSpace}</p>
33 </div>
34 );
35}
36
37// Sposob 3: Arrow function z jawnym typem
38const CrewBadgeArrow = ({ name, rank, specialization, yearsInSpace }: CrewMemberProps): JSX.Element => {
39 return (
40 <div className="badge-card">
41 <div className="badge-header">Arrow</div>
42 <h3>{name}</h3>
43 <p className="rank">{rank}</p>
44 <p>Specjalizacja: {specialization}</p>
45 <p>Lata w kosmosie: {yearsInSpace}</p>
46 </div>
47 );
48};
49
50function App() {
51 const member: CrewMemberProps = {
52 name: "Nova",
53 rank: "Kapitan",
54 specialization: "Nawigacja",
55 yearsInSpace: 15
56 };
57
58 return (
59 <div className="app">
60 <h1>Sposoby typowania komponentow</h1>
61 <div className="cards">
62 <CrewBadgeFC {...member} />
63 <CrewBadgeExplicit {...member} />
64 <CrewBadgeArrow {...member} />
65 </div>
66 <p className="note">Wszystkie trzy sposoby daja ten sam rezultat. Zalecany jest sposob z jawnym typowaniem (Explicit).</p>
67 </div>
68 );
69}
70
71export default App;Pamiętaj: typ props to instrukcja obsługi modułu - przyklej ją do parametru, a każdy członek załogi wie, jak go podłączyć.
Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Jakie rozszerzenie pliku używa się dla komponentów React napisanych w TypeScript?
2. Który sposób typowania komponentów funkcyjnych jest obecnie zalecany przez społeczność React?
Zadania praktyczne w grze
- Edytor kodu
Podstawowe typowanie interfejsów i komponentów
- Klikanie w kolejności
Ułóż składnię definicji interfejsu w TypeScript: