Kurs JavaScript i React · Moduł 12: React i TypeScript

Typowanie Komponentów Funkcyjnych

4 min czytania
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:

  1. 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
  2. Jawne typowanie jest bardziej czytelne - widać dokładnie, co komponent przyjmuje
  3. 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. 1. Jakie rozszerzenie pliku używa się dla komponentów React napisanych w TypeScript?

  2. 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:

Przydatne artykuły