Ustawianie TypeScriptu dla nowoczesnych projektów React za pomocą Webpack

Opublikowany: 2022-03-10
Krótkie podsumowanie ↬ W tym artykule przedstawiono Typescript, indeks górny języka JavaScript, który przedstawia funkcję typu statycznego do wykrywania typowych błędów w kodach programistów, co zwiększa wydajność, a tym samym zapewnia niezawodne aplikacje korporacyjne. Dowiesz się również, jak skutecznie skonfigurować TypeScript w projekcie React, gdy budujemy aplikację Money Heist Episode Picker App, eksplorując TypeScript, haki React, takie jak useReducer, useContext i Reach Router.

W erze tworzenia oprogramowania JavaScript może być używany do tworzenia prawie każdego rodzaju aplikacji. Jednak fakt, że JavaScript jest typowany dynamicznie, może być problemem dla większości dużych przedsiębiorstw ze względu na jego luźną funkcję sprawdzania typu.

Na szczęście nie musimy czekać, aż Komitet Techniczny Ecma 39 wprowadzi do JavaScriptu system typów statycznych. Zamiast tego możemy użyć TypeScript.

JavaScript, jako dynamicznie typowany, nie jest świadomy typu danych zmiennej, dopóki ta zmienna nie zostanie utworzona w czasie wykonywania. Deweloperzy, którzy piszą duże programy, mogą mieć tendencję do ponownego przypisywania zmiennej, zadeklarowanej wcześniej, do wartości innego typu, bez żadnego ostrzeżenia czy problemu, co skutkuje często pomijanymi błędami.

W tym samouczku dowiemy się, czym jest TypeScript i jak z nim pracować w projekcie React. Na koniec zbudujemy projekt składający się z aplikacji do wybierania odcinków programu telewizyjnego Money Heist , wykorzystującej TypeScript i aktualne hooki w stylu React ( useState , useEffect , useReducer , useContext ). Mając tę ​​wiedzę, możesz eksperymentować z TypeScript we własnych projektach.

Ten artykuł nie jest wprowadzeniem do języka TypeScript. Dlatego nie będziemy przechodzić przez podstawową składnię TypeScript i JavaScript. Jednak nie musisz być ekspertem w żadnym z tych języków, aby kontynuować, ponieważ postaramy się postępować zgodnie z zasadą KISS (niech to będzie proste, głupie).

Więcej po skoku! Kontynuuj czytanie poniżej ↓

Co to jest TypeScript?

W 2019 r. TypeScript znalazł się na siódmym najczęściej używanym języku i piątym najszybciej rozwijającym się języku w serwisie GitHub. Ale czym właściwie jest TypeScript?

Zgodnie z oficjalną dokumentacją, TypeScript to typowany nadzbiór JavaScript, który kompiluje się do zwykłego JavaScript. Jest rozwijany i utrzymywany przez Microsoft i społeczność open-source.

„Nadzbiór” w tym kontekście oznacza, że ​​język zawiera wszystkie cechy i funkcje JavaScript, a także niektóre. TypeScript to typowany język skryptowy.

Oferuje programistom większą kontrolę nad ich bazą kodu poprzez adnotacje typu, klasy i interfejs, oszczędzając programistom konieczności ręcznego naprawiania irytujących błędów w konsoli.

TypeScript nie został stworzony do zmiany JavaScript. Zamiast tego rozszerza JavaScript o cenne nowe funkcje. Każdy program napisany w zwykłym JavaScript będzie również działał zgodnie z oczekiwaniami w TypeScript, w tym międzyplatformowe aplikacje mobilne i zaplecza w Node.js.

Oznacza to, że możesz również pisać aplikacje React w TypeScript, tak jak zrobimy w tym samouczku.

Dlaczego TypeScript?

Być może nie jesteś przekonany, że skorzystasz z dobrodziejstw TypeScriptu. Rozważmy kilka jego zalet.

Mniej błędów

Nie możemy wyeliminować wszystkich błędów w naszym kodzie, ale możemy je zredukować. TypeScript sprawdza typy w czasie kompilacji i zgłasza błędy, jeśli zmieni się typ zmiennej.

Możliwość znalezienia tych oczywistych, ale częstych błędów na tak wczesnym etapie znacznie ułatwia zarządzanie kodem za pomocą typów.

Refaktoryzacja jest łatwiejsza

Prawdopodobnie często chcesz dokonać refaktoryzacji wielu rzeczy, ale ponieważ dotyczą one tak wielu innych kodów i wielu innych plików, obawiasz się ich modyfikowania.

W TypeScript takie rzeczy można często zmienić za pomocą jednego kliknięcia polecenia „Zmień nazwę symbolu” w zintegrowanym środowisku programistycznym (IDE).

Zmiana nazwy aplikacji na expApp (duży podgląd)

W dynamicznie typowanym języku, takim jak JavaScript, jedynym sposobem na refaktoryzację wielu plików jednocześnie jest tradycyjna funkcja „wyszukaj i zamień” przy użyciu wyrażeń regularnych (RegExp).

W języku statycznie pisanym, takim jak TypeScript, „wyszukaj i zamień” nie jest już potrzebne. Za pomocą poleceń IDE, takich jak „Znajdź wszystkie wystąpienia” i „Zmień nazwę symbolu”, możesz zobaczyć wszystkie wystąpienia w aplikacji danej funkcji, klasy lub właściwości interfejsu obiektu.

TypeScript pomoże Ci znaleźć wszystkie wystąpienia zrefaktoryzowanego bitu, zmienić jego nazwę i ostrzeże Cię o błędzie kompilacji w przypadku, gdy Twój kod ma niezgodności typu po refaktoryzacji.

TypeScript ma jeszcze więcej zalet niż to, co tutaj omówiliśmy.

Wady TypeScript

TypeScript z pewnością nie jest pozbawiony wad, nawet biorąc pod uwagę obiecujące funkcje wymienione powyżej.

Fałszywe poczucie bezpieczeństwa

Funkcja sprawdzania typu TypeScript często tworzy fałszywe poczucie bezpieczeństwa wśród programistów. Sprawdzanie typu rzeczywiście ostrzega nas, gdy coś jest nie tak z naszym kodem. Jednak typy statyczne nie zmniejszają ogólnej gęstości błędów.

Dlatego siła twojego programu będzie zależeć od użycia TypeScript, ponieważ typy są pisane przez programistę i nie są sprawdzane w czasie wykonywania.

Jeśli szukasz języka TypeScript w celu zmniejszenia liczby błędów, rozważ zamiast tego programowanie oparte na testach.

Skomplikowany system pisania

System pisania, mimo że pod wieloma względami jest świetnym narzędziem, czasami może być nieco skomplikowany. Ta wada wynika z tego, że jest w pełni interoperacyjna z JavaScript, co pozostawia jeszcze więcej miejsca na komplikacje.

Jednak TypeScript to nadal JavaScript, więc zrozumienie JavaScript jest ważne.

Kiedy używać TypeScriptu?

Radziłbym używać TypeScript w następujących przypadkach:

  • Jeśli chcesz zbudować aplikację, która będzie utrzymywana przez długi czas , zdecydowanie polecam zacząć od TypeScript, ponieważ wspiera on samodokumentowanie kodu, pomagając w ten sposób innym programistom w łatwym zrozumieniu Twojego kodu, gdy dołączą do Twojej bazy kodu .
  • Jeśli chcesz utworzyć bibliotekę , rozważ napisanie jej w języku TypeScript. Pomoże to edytorom kodu zasugerować odpowiednie typy programistom korzystającym z Twojej biblioteki.

W kilku ostatnich sekcjach zrównoważyliśmy zalety i wady TypeScript. Przejdźmy do dnia dzisiejszego: konfiguracja TypeScript w nowoczesnym projekcie React .

Pierwsze kroki

Istnieje kilka sposobów na skonfigurowanie TypeScript w projekcie React. W tym samouczku omówimy tylko dwa.

Metoda 1: Utwórz aplikację React + TypeScript

Około dwa lata temu zespół React wydał aplikację Create React App 2.1 z obsługą TypeScript. Być może nigdy nie będziesz musiał wykonywać żadnych ciężkich prac, aby wprowadzić TypeScript do swojego projektu.

Zapowiedź TypeScript w aplikacji Create React (duży podgląd)

Aby rozpocząć nowy projekt Create React App, możesz uruchomić to…

 npx create-react-app my-app --folder-name

… albo to:

 yarn create react-app my-app --folder-name

Aby dodać TypeScript do projektu Create React App, najpierw zainstaluj go i odpowiednie @types :

 npm install --save typescript @types/node @types/react @types/react-dom @types/jest

… lub:

 yarn add typescript @types/node @types/react @types/react-dom @types/jest

Następnie zmień nazwy plików (na przykład index.js na index.tsx ) i zrestartuj serwer deweloperski !

To było szybkie, prawda?

Metoda 2: Skonfiguruj TypeScript za pomocą Webpack

Webpack to statyczny pakiet modułów dla aplikacji JavaScript. Pobiera cały kod z Twojej aplikacji i sprawia, że ​​można go używać w przeglądarce internetowej. Moduły to fragmenty kodu, które można ponownie wykorzystać, utworzone na podstawie kodu JavaScript, node_modules , obrazów i stylów CSS Twojej aplikacji, które są spakowane tak, aby można je było łatwo używać w Twojej witrynie.

Utwórz nowy projekt

Zacznijmy od stworzenia nowego katalogu dla naszego projektu:

 mkdir react-webpack cd react-webpack

Użyjemy npm do zainicjowania naszego projektu:

 npm init -y

Powyższe polecenie wygeneruje plik package.json z pewnymi wartościami domyślnymi. Dodajmy też zależności dla webpacka, TypeScriptu i niektórych modułów specyficznych dla Reacta.

Instalowanie pakietów

Na koniec musimy zainstalować niezbędne pakiety. Otwórz interfejs wiersza poleceń (CLI) i uruchom to:

 #Installing devDependencies npm install --save-dev @types/react @types/react-dom awesome-typescript-loader css-loader html-webpack-plugin mini-css-extract-plugin source-map-loader typescript webpack webpack-cli webpack-dev-server #installing Dependencies npm install react react-dom

Dodajmy też ręcznie kilka różnych plików i folderów w naszym folderze react-webpack :

  1. Dodaj webpack.config.js , aby dodać konfiguracje związane z webpackiem.
  2. Dodaj tsconfig.json dla wszystkich naszych konfiguracji TypeScript.
  3. Dodaj nowy katalog, src .
  4. Utwórz nowy katalog, components , w folderze src .
  5. Na koniec dodaj index.html , App.tsx i index.tsx w folderze components .

Struktura projektu

W ten sposób nasza struktura folderów będzie wyglądać mniej więcej tak:

 ├── package.json ├── package-lock.json ├── tsconfig.json ├── webpack.config.js ├── .gitignore └── src └──components ├── App.tsx ├── index.tsx ├── index.html

Zacznij dodawać kod

Zaczniemy od index.html :

 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>React-Webpack Setup</title> </head> <body> <div></div> </body> </html>

Spowoduje to utworzenie kodu HTML z pustym div z identyfikatorem output .

Dodajmy kod do naszego komponentu React App.tsx :

 import * as React from "react"; export interface HelloWorldProps { userName: string; lang: string; } export const App = (props: HelloWorldProps) => ( <h1> Hi {props.userName} from React! Welcome to {props.lang}! </h1> );

Stworzyliśmy obiekt interfejsu i nazwaliśmy go HelloWorldProps , przy czym userName i lang mają typ string .

Przekazaliśmy props do naszego komponentu App i wyeksportowaliśmy go.

Teraz zaktualizujmy kod w index.tsx :

 import * as React from "react"; import * as ReactDOM from "react-dom"; import { App } from "./App"; ReactDOM.render( <App userName="Beveloper" lang="TypeScript" />, document.getElementById("output") );

Właśnie zaimportowaliśmy komponent App do index.tsx . Gdy webpack zobaczy dowolny plik z rozszerzeniem .ts lub .tsx , przetranspiluje ten plik za pomocą biblioteki awesome-typescript-loader.

Konfiguracja TypeScript

Następnie dodamy trochę konfiguracji do tsconfig.json :

 { "compilerOptions": { "jsx": "react", "module": "commonjs", "noImplicitAny": true, "outDir": "./build/", "preserveConstEnums": true, "removeComments": true, "sourceMap": true, "target": "es5" }, "include": [ "src/components/index.tsx" ] }

Przyjrzyjmy się również różnym opcjom, które dodaliśmy do tsconfig.json :

  • compilerOptions reprezentuje różne opcje kompilatora.
  • jsx:react Dodaje obsługę JSX w plikach .tsx .
  • lib Dodaje listę plików bibliotecznych do kompilacji (na przykład użycie es2015 pozwala na użycie składni ECMAScript 6).
  • module Generuje kod modułu.
  • noImplicitAny Zgłasza błędy dla deklaracji z domniemanym any typem.
  • outDir Reprezentuje katalog wyjściowy.
  • sourceMap Generuje plik .map , który może być bardzo przydatny do debugowania aplikacji.
  • target Reprezentuje docelową wersję ECMAScript, do której ma zostać przetranspilowany nasz kod (możemy dodać wersję opartą na konkretnych wymaganiach przeglądarki).
  • include Używany do określania listy plików do uwzględnienia.

Konfiguracja pakietu internetowego

Dodajmy trochę konfiguracji webpacka do webpack.config.js .

 const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = { entry: "./src/components/index.tsx", target: "web", mode: "development", output: { path: path.resolve(\__dirname, "build"), filename: "bundle.js", }, resolve: { extensions: [".js", ".jsx", ".json", ".ts", ".tsx"], }, module: { rules: [ { test: /\.(ts|tsx)$/, loader: "awesome-typescript-loader", }, { enforce: "pre", test: /\.js$/, loader: "source-map-loader", }, { test: /\.css$/, loader: "css-loader", }, ], }, plugins: [ new HtmlWebpackPlugin({ template: path.resolve(\__dirname, "src", "components", "index.html"), }), new MiniCssExtractPlugin({ filename: "./src/yourfile.css", }), ], };

Przyjrzyjmy się różnym opcjom, które dodaliśmy do webpack.config.js :

  • entry Określa punkt wejścia dla naszej aplikacji. Może to być pojedynczy plik lub tablica plików, które chcemy uwzględnić w naszej kompilacji.
  • output Zawiera konfigurację wyjścia. Aplikacja patrzy na to, próbując wyprowadzić dołączony kod z naszego projektu na dysk. Ścieżka reprezentuje katalog wyjściowy, do którego kod ma zostać wyprowadzony, a nazwa pliku reprezentuje nazwę pliku. Zwykle nazywa się to bundle.js .
  • resolve Webpack sprawdza ten atrybut, aby zdecydować, czy spakować, czy pominąć plik. Dlatego w naszym projekcie webpack będzie uwzględniał pliki z rozszerzeniami .js , .jsx , .json , .ts , i .tsx do łączenia.
  • module Możemy umożliwić webpackowi ładowanie określonego pliku na żądanie aplikacji, za pomocą programów ładujących. Pobiera obiekt reguł, który określa, że:
    • każdy plik, który kończy się rozszerzeniem .tsx lub .ts , powinien używać awesome-typescript-loader do załadowania;
    • pliki, które kończą się rozszerzeniem .js , należy ładować za pomocą source-map-loader ;
    • pliki kończące się rozszerzeniem .css należy ładować za pomocą css-loader .
  • plugins Webpack ma swoje ograniczenia i udostępnia wtyczki, które je przezwyciężają i rozszerzają jego możliwości. Na przykład html-webpack-plugin tworzy plik szablonu, który jest renderowany w przeglądarce z pliku index.html w katalogu ./src/component/index.html .

MiniCssExtractPlugin renderuje nadrzędny plik CSS aplikacji.

Dodawanie skryptów do pliku package.json

Możemy dodać różne skrypty do budowania aplikacji React w naszym pliku package.json :

 "scripts": { "start": "webpack-dev-server --open", "build": "webpack" },

Teraz uruchom npm start w swoim CLI. Jeśli wszystko poszło dobrze, powinieneś zobaczyć to:

Dane wyjściowe konfiguracji React-Webpack (duży podgląd)

Jeśli masz smykałkę do webpacka, sklonuj repozytorium dla tej konfiguracji i używaj go w swoich projektach.

Tworzenie plików

Utwórz folder src i plik index.tsx . Będzie to podstawowy plik, który renderuje Reacta.

Teraz, jeśli uruchomimy npm start , uruchomi nasz serwer i otworzy nową kartę. Uruchomienie npm run build zbuduje webpack dla produkcji i utworzy dla nas folder build.

Widzieliśmy, jak skonfigurować TypeScript od podstaw za pomocą metody konfiguracji Create React App i webpack.

Jednym z najszybszych sposobów na pełne zrozumienie języka TypeScript jest przekonwertowanie jednego z istniejących projektów vanilla React na TypeScript. Niestety, stopniowe przyjmowanie TypeScriptu w istniejącym projekcie vanilla React jest stresujące, ponieważ pociąga za sobą konieczność wyrzucenia lub zmiany nazwy wszystkich plików, co skutkowałoby konfliktami i gigantycznym pull requestem, gdyby projekt należał do dużego zespołu.

Następnie przyjrzymy się, jak łatwo przenieść projekt React do TypeScript.

Przenieś istniejącą aplikację Create React do TypeScript

Aby ułatwić zarządzanie tym procesem, podzielimy go na etapy, które umożliwią nam migrację w poszczególnych porcjach. Oto kroki, które podejmiemy, aby przeprowadzić migrację naszego projektu:

  1. Dodaj TypeScript i typy.
  2. Dodaj tsconfig.json .
  3. Zacznij od małych.
  4. Zmień nazwę rozszerzenia plików na .tsx .

1. Dodaj TypeScript do projektu

Najpierw musimy dodać TypeScript do naszego projektu. Zakładając, że Twój projekt React został załadowany za pomocą aplikacji Create React, możemy uruchomić:

 # Using npm npm install --save typescript @types/node @types/react @types/react-dom @types/jest # Using Yarn yarn add typescript @types/node @types/react @types/react-dom @types/jest

Zwróć uwagę, że nie zmieniliśmy jeszcze niczego w TypeScript. Jeśli uruchomimy polecenie uruchomienia projektu lokalnie ( npm start lub yarn start ), nic się nie zmieni. Jeśli tak jest, to świetnie! Jesteśmy gotowi na kolejny krok.

2. Dodaj plik tsconfig.json

Przed skorzystaniem z TypeScriptu musimy go skonfigurować za pomocą pliku tsconfig.json . Najprostszym sposobem na rozpoczęcie jest utworzenie szkieletu za pomocą tego polecenia:

 npx tsc --init

To daje nam podstawy, z dużą ilością komentowanego kodu. Teraz zastąp cały kod w tsconfig.json następującym:

 { "compilerOptions": { "jsx": "react", "module": "commonjs", "noImplicitAny": true, "outDir": "./build/", "preserveConstEnums": true, "removeComments": true, "sourceMap": true, "target": "es5" }, "include": [ "./src/**/**/\*" ] }

Konfiguracja TypeScript

Przyjrzyjmy się również różnym opcjom, które dodaliśmy do tsconfig.json :

  • compilerOptions reprezentuje różne opcje kompilatora.
    • target Tłumaczy nowsze konstrukcje JavaScript do starszej wersji, takiej jak ECMAScript 5.
    • lib Dodaje listę plików bibliotecznych do kompilacji (na przykład użycie es2015 pozwala na użycie składni ECMAScript 6).
    • jsx:react Dodaje obsługę JSX w plikach .tsx .
    • lib Dodaje listę plików bibliotecznych do kompilacji (na przykład użycie es2015 pozwala na użycie składni ECMAScript 6).
    • module Generuje kod modułu.
    • noImplicitAny Służy do zgłaszania błędów dla deklaracji z domniemanym any typem.
    • outDir Reprezentuje katalog wyjściowy.
    • sourceMap Generuje plik .map , który może być bardzo przydatny do debugowania naszej aplikacji.
    • include Używany do określania listy plików do uwzględnienia.

Opcje konfiguracji będą się różnić w zależności od wymagań projektu. Być może trzeba będzie sprawdzić arkusz kalkulacyjny opcji TypeScript, aby dowiedzieć się, co pasowałoby do Twojego projektu.

Podjęliśmy tylko wymagane działania, aby wszystko było gotowe. Naszym następnym krokiem jest migracja pliku do TypeScript.

3. Zacznij od prostego komponentu

Skorzystaj z możliwości stopniowego przyswajania języka TypeScript. Idź po jednym pliku na raz we własnym tempie. Rób to, co ma sens dla Ciebie i Twojego zespołu. Nie próbuj zajmować się tym wszystkim na raz.

Aby poprawnie to przekonwertować, musimy zrobić dwie rzeczy:

  1. Zmień rozszerzenie pliku na .tsx .
  2. Dodaj adnotację typu (co wymagałoby pewnej znajomości języka TypeScript).

4. Zmień nazwy rozszerzeń plików na .tsx

W dużej bazie kodu może wydawać się męcząca zmiana nazw plików.

Zmień nazwy wielu plików w systemie macOS

Zmiana nazw wielu plików może być stratą czasu. Oto, jak możesz to zrobić na komputerze Mac. Kliknij prawym przyciskiem myszy (lub Ctrl + kliknięcie lub kliknij dwoma palcami jednocześnie na gładziku, jeśli używasz MacBooka) na folderze zawierającym pliki, których nazwy chcesz zmienić. Następnie kliknij „Pokaż w Finderze”. W Finderze zaznacz wszystkie pliki, których nazwy chcesz zmienić. Kliknij wybrane pliki prawym przyciskiem myszy i wybierz „Zmień nazwę X elementów…” Następnie zobaczysz coś takiego:

Zmień nazwy plików na komputerze Mac (duży podgląd)

Wstaw ciąg, który chcesz znaleźć, i ciąg, którym chcesz zastąpić ten znaleziony ciąg, i naciśnij "Zmień nazwę". Gotowy.

Zmień nazwy wielu plików w systemie Windows

Zmiana nazw wielu plików w systemie Windows wykracza poza zakres tego samouczka, ale dostępny jest kompletny przewodnik. Zwykle po zmianie nazw plików pojawiają się błędy; wystarczy dodać adnotacje typu. Możesz to odświeżyć w dokumentacji.

Omówiliśmy, jak skonfigurować TypeScript w aplikacji React. Teraz zbudujmy aplikację do wybierania odcinków dla Money Heist za pomocą TypeScript.

Nie będziemy omawiać podstawowych typów TypeScript. Przejrzenie dokumentacji przed kontynuowaniem tego samouczka jest wymagane.

Czas na budowę

Aby proces ten wydawał się mniej zniechęcający, podzielimy go na etapy, które pozwolą nam zbudować aplikację w pojedynczych fragmentach. Oto wszystkie kroki, które podejmiemy, aby zbudować selektor epizodów Money Heist :

  • Zbuduj aplikację Create React.
  • Pobierz odcinki.
    • Utwórz odpowiednie typy i interfejsy dla naszych odcinków w interface.ts .
    • Skonfiguruj sklep do pobierania odcinków w store.tsx .
    • Utwórz akcję pobierania odcinków w action.ts .
    • Utwórz składnik EpisodeList.tsx , który przechowuje pobrane odcinki.
    • Zaimportuj komponent EpisodesList na naszą stronę główną za pomocą React Lazy and Suspense .
  • Dodaj odcinki.
    • Skonfiguruj sklep, aby dodać odcinki w store.tsx .
    • Utwórz akcję dodawania odcinków w action.ts .
  • Usuń odcinki.
    • Skonfiguruj sklep do usuwania odcinków w store.tsx .
    • Utwórz akcję usuwania odcinków w action.ts .
  • Ulubiony odcinek.
    • Importuj komponent EpisodesList do ulubionego odcinka.
    • Renderuj EpisodesList w ulubionym odcinku.
  • Korzystanie z routera Reach do nawigacji.

Skonfiguruj React

Najłatwiejszym sposobem skonfigurowania Reacta jest użycie aplikacji Create React. Create React App to oficjalnie obsługiwany sposób tworzenia jednostronicowych aplikacji React. Oferuje nowoczesną konfigurację kompilacji bez konfiguracji.

Wykorzystamy go do załadowania aplikacji, którą będziemy budować. W swoim CLI uruchom poniższe polecenie:

 npx create-react-app react-ts-app && cd react-ts-app

Po pomyślnym zakończeniu instalacji uruchom serwer React, uruchamiając npm start .

Strona startowa React (duży podgląd)

Zrozumienie interfejsów i typów w maszynopisie

Interfejsy w TypeScript są używane, gdy musimy nadać typy właściwościom obiektów. Dlatego używalibyśmy interfejsów do definiowania naszych typów.

 interface Employee { name: string, role: string salary: number } const bestEmployee: Employee= { name: 'John Doe', role: 'IOS Developer', salary: '$8500' //notice we are using a string }

Podczas kompilowania powyższego kodu zobaczylibyśmy ten błąd: „Rodzaje salary nieruchomości są niezgodne. Typ string nie może być przypisany do typu number ”.

Takie błędy występują w języku TypeScript, gdy właściwość lub zmienna ma przypisany typ inny niż zdefiniowany. W szczególności powyższy fragment kodu oznacza, że ​​do właściwości salary przypisano typ string zamiast typu number .

Stwórzmy plik interface.ts w naszym folderze src . Skopiuj i wklej do niego ten kod:

 /** |-------------------------------------------------- | All the interfaces! |-------------------------------------------------- */ export interface IEpisode { airdate: string airstamp: string airtime: string id: number image: { medium: string; original: string } name: string number: number runtime: number season: number summary: string url: string } export interface IState { episodes: Array<IEpisode> favourites: Array<IEpisode> } export interface IAction { type: string payload: Array<IEpisode> | any } export type Dispatch = React.Dispatch<IAction> export type FavAction = ( state: IState, dispatch: Dispatch, episode: IEpisode ) => IAction export interface IEpisodeProps { episodes: Array<IEpisode> store: { state: IState; dispatch: Dispatch } toggleFavAction: FavAction favourites: Array<IEpisode> } export interface IProps { episodes: Array<IEpisode> store: { state: IState; dispatch: Dispatch } toggleFavAction: FavAction favourites: Array<IEpisode> }

Dobrą praktyką jest dodanie „I” do nazwy interfejsu. Sprawia, że ​​kod jest czytelny. Możesz jednak zdecydować się na jego wykluczenie.

Interfejs IEpisode

Nasz interfejs API zwraca zestaw właściwości, takich jak airdate , airstamp , airtime , id , image , name , number , runtime , season , summary i url . Dlatego zdefiniowaliśmy interfejs IEpisode i ustawiliśmy odpowiednie typy danych we właściwościach obiektu.

IState Interface

Nasz interfejs IState ma odpowiednio właściwości episodes i favorites oraz interfejs Array<IEpisode> .

Iakcja

Właściwości interfejsu IAction to payload i type . Właściwość type ma typ ciągu, podczas gdy ładunek ma typ Array | any Array | any .

Zauważ, że Array | any Array | any oznacza tablicę interfejsu odcinka lub dowolny typ.

Typ Dispatch jest ustawiony na React.Dispatch i interfejs <IAction> . Zwróć uwagę, że React.Dispatch jest standardowym typem funkcji dispatch , zgodnie z bazą kodu @types/react , podczas gdy <IAction> jest tablicą akcji Interface.

Ponadto Visual Studio Code ma moduł sprawdzania TypeScript. Tak więc wystarczy podświetlić lub najechać kursorem na kod, aby zasugerować odpowiedni typ.

Innymi słowy, abyśmy mogli korzystać z naszego interfejsu w naszych aplikacjach, musimy go wyeksportować. Do tej pory mamy nasz sklep i nasze interfejsy, które przechowują typ naszego obiektu. Stwórzmy teraz nasz sklep. Zauważ, że inne interfejsy są zgodne z tymi samymi konwencjami, które wyjaśniono.

Pobierz odcinki

Tworzenie sklepu

Aby pobrać nasze odcinki, potrzebujemy magazynu, który przechowuje początkowy stan danych i definiuje naszą funkcję reduktora.

Aby to ustawić, użyjemy haka useReducer . Utwórz plik store.tsx w folderze src . Skopiuj i wklej do niego następujący kod.

 import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext (initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload } default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} } import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext (initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload } default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} } import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext (initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload } default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} } import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext (initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload } default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} }

Oto kroki, które podjęliśmy, aby stworzyć sklep:

  • Definiując nasz sklep potrzebujemy useReducer oraz API createContext z Reacta, dlatego go zaimportowaliśmy.
  • Zaimportowaliśmy IState i IAction z ./types/interfaces .
  • Zadeklarowaliśmy obiekt initialState z typem IState oraz właściwościami epizodów i ulubionych, które są odpowiednio ustawione na pustą tablicę.
  • Następnie utworzyliśmy zmienną Store , która przechowuje metodę createContext i przekazaną wartość initialState .

Typ metody createContext to <IState | any> <IState | any> , co oznacza, że ​​może to być typ <IState> lub any . Zobaczymy any typ często używany w tym artykule.

  • Następnie zadeklarowaliśmy funkcję reducer i przekazaliśmy state i action jako parametry. Funkcja reducer ma instrukcję switch, która sprawdza wartość action.type . Jeśli wartością jest FETCH_DATA , zwraca obiekt, który ma kopię naszego stanu (...state) oraz stanu epizodu, który zawiera ładunek akcji.
  • W instrukcji switch zwracamy stan default .

Należy zauważyć, że parametry state i action w funkcji reduktora mają odpowiednio typy IState i IAction . Ponadto funkcja reducer ma typ IState .

  • Na koniec zadeklarowaliśmy funkcję StoreProvider . Dzięki temu wszystkie komponenty naszej aplikacji będą miały dostęp do sklepu.
  • Ta funkcja przyjmuje children jako właściwość, a wewnątrz funkcji StorePrivder zadeklarowaliśmy hak useReducer .
  • Zdestrukturyzowaliśmy state i dispatch .
  • Aby nasz sklep był dostępny dla wszystkich komponentów, przekazaliśmy wartość obiektu zawierającą state i dispatch .

state zawierający nasze odcinki i stan ulubionych zostanie udostępniony przez inne komponenty, natomiast dispatch jest funkcją zmieniającą stan.

  • Wyeksportujemy Store i StoreProvider , aby można było z nich korzystać w całej naszej aplikacji.

Utwórz Action.ts

Będziemy musieli wysyłać żądania do interfejsu API, aby pobrać odcinki, które będą wyświetlane użytkownikowi. Zostanie to zrobione w pliku akcji. Utwórz plik Action.ts , a następnie wklej następujący kod:

 import { Dispatch } from './interface/interfaces' export const fetchDataAction = async (dispatch: Dispatch) => { const URL = 'https://api.tvmaze.com/singlesearch/shows?q=la-casa-de-papel&embed=episodes' const data = await fetch(URL) const dataJSON = await data.json() return dispatch({ type: 'FETCH_DATA', payload: dataJSON.\_embedded.episodes }) }

Najpierw musimy zaimportować nasze interfejsy, aby można było ich użyć w tym pliku. Aby utworzyć akcję, wykonano następujące kroki:

  • Funkcja fetchDataAction przyjmuje właściwości dispatch jako parametr.
  • Ponieważ nasza funkcja jest asynchroniczna, async i await .
  • Tworzymy zmienną ( URL ), która przechowuje nasz punkt końcowy API.
  • Mamy inną zmienną o nazwie data , która przechowuje odpowiedź z API.
  • Następnie przechowujemy odpowiedź JSON w dataJSON , po otrzymaniu odpowiedzi w formacie JSON przez wywołanie data.json() .
  • Na koniec zwracamy funkcję wysyłającą, która ma właściwość type i ciąg FETCH_DATA . Posiada również payload() . _embedded.episodes to tablica obiektów epizodów z naszego endpoint .

Zauważ, że funkcja fetchDataAction pobiera nasz punkt końcowy, konwertuje go na obiekty JSON i zwraca funkcję wysyłania, która aktualizuje stan zadeklarowany wcześniej w Store.

Typ eksportowanej wysyłki jest ustawiony na React.Dispatch . Należy zauważyć, że React.Dispatch jest standardowym typem funkcji wysyłania zgodnie z bazą kodu @types/react , podczas gdy <IAction> jest tablicą akcji interfejsu.

Składnik listy odcinków

Aby zachować możliwość ponownego wykorzystania naszej aplikacji, wszystkie pobrane odcinki będziemy przechowywać w osobnym pliku, a następnie zaimportujemy plik w naszym komponencie homePage .

W folderze components utwórz plik EpisodesList.tsx , a następnie skopiuj i wklej do niego następujący kod:

 import React from 'react' import { IEpisode, IProps } from '../types/interfaces' const EpisodesList = (props: IProps): Array<JSX.Element> => { const { episodes } = props return episodes.map((episode: IEpisode) => { return ( <section key={episode.id} className='episode-box'> <img src={!!episode.image ? episode.image.medium : ''} alt={`Money Heist ${episode.name}`} /> <div>{episode.name}</div> <section style={{ display: 'flex', justifyContent: 'space-between' }}> <div> Season: {episode.season} Number: {episode.number} </div> <button type='button' > Fav </button> </section> </section> ) }) } export default EpisodesList
  • Importujemy IEpisode i IProps z interfaces.tsx .
  • Następnie tworzymy funkcję EpisodesList , która pobiera rekwizyty. Rekwizyty będą miały typ IProps , podczas gdy funkcja ma typ Array<JSX.Element> .

Visual Studio Code sugeruje, aby nasz typ funkcji był zapisany jako JSX.Element[] .

Visual Studio Code sugeruje typ (duży podgląd)

Chociaż Array<JSX.Element> jest równy JSX.Element[] , Array<JSX.Element> jest nazywany tożsamością ogólną. Dlatego w tym artykule często będzie używany wzorzec ogólny.

  • Wewnątrz funkcji destrukturyzujemy episodes z props , których typem jest IEpisode .

Przeczytaj o tożsamości ogólnej. Ta wiedza będzie potrzebna w miarę postępów.

  • Zwróciliśmy rekwizyty episodes i zmapowaliśmy je, aby zwrócić kilka tagów HTML.
  • Pierwsza sekcja zawiera key , którym jest episode.id , oraz className episode-box , który zostanie utworzony później. Wiemy, że nasze odcinki mają obrazy; stąd tag obrazu.
  • Obraz ma trójskładnikowy operator, który sprawdza, czy istnieje episode.image lub episode.image.medium . W przeciwnym razie wyświetlamy pusty ciąg, jeśli nie zostanie znaleziony żaden obraz. Ponadto dołączyliśmy episode.name do div.

W section pokazujemy sezon, do którego należy odcinek oraz jego numer. Mamy przycisk z tekstem Fav . Wyeksportowaliśmy komponent EpisodesList , dzięki czemu możemy go używać w całej naszej aplikacji.

Komponent strony głównej

Chcemy, aby strona główna uruchamiała wywołanie API i wyświetlała odcinki za pomocą utworzonego przez nas komponentu EpisodesList . Wewnątrz folderu components utwórz składnik HomePage , a następnie skopiuj i wklej do niego następujący kod:

 import React, { useContext, useEffect, lazy, Suspense } from 'react' import App from '../App' import { Store } from '../Store' import { IEpisodeProps } from '../types/interfaces' import { fetchDataAction } from '../Actions' const EpisodesList = lazy<any>(() => import('./EpisodesList')) const HomePage = (): JSX.Element => { const { state, dispatch } = useContext(Store) useEffect(() => { state.episodes.length === 0 && fetchDataAction(dispatch) }) const props: IEpisodeProps = { episodes: state.episodes, store: { state, dispatch } } return ( <App> <Suspense fallback={<div>loading...</div>}> <section className='episode-layout'> <EpisodesList {...props} /> </section> </Suspense> </App> ) } export default HomePage
  • Importujemy useContext , useEffect , lazy i Suspense z Reacta. Importowany składnik aplikacji jest podstawą, na której wszystkie inne składniki muszą otrzymać wartość sklepu.
  • Importujemy również Store , IEpisodeProps i FetchDataAction z ich odpowiednich plików.
  • Importujemy komponent EpisodesList za pomocą funkcji React.lazy dostępnej w React 16.6.

Lazy loading React obsługuje konwencję dzielenia kodu. W ten sposób nasz komponent EpisodesList jest ładowany dynamicznie, a nie od razu, co poprawia wydajność naszej aplikacji.

  • Destrukturyzujemy state i dispatch jako rekwizyty ze Store .
  • Znak ampersand (&&) w haczyku useEffect sprawdza, czy nasz stan epizodów jest empty (lub równy 0). W przeciwnym razie zwracamy funkcję fetchDataAction .
  • Na koniec zwracamy komponent App . Wewnątrz używamy opakowania Suspense i ustawiamy fallback na div z tekstem loading . Zostanie to wyświetlone użytkownikowi podczas oczekiwania na odpowiedź z interfejsu API.
  • Komponent EpisodesList zostanie zamontowany, gdy dane będą dostępne, a dane, które będą zawierać episodes , zostaną w nim rozprowadzone.

Skonfiguruj Index.txt

Składnik Homepage musi być elementem podrzędnym StoreProvider . Musimy to zrobić w pliku index . Zmień nazwę index.js na index.tsx i wklej następujący kod:

 import React from 'react' import ReactDOM from 'react-dom' import './index.css' import { StoreProvider } from './Store' import HomePage from './components/HomePage' ReactDOM.render( <StoreProvider> <HomePage /> </StoreProvider>, document.getElementById('root') )

Importujemy StoreProvider , HomePage i index.css z ich odpowiednich plików. We wrap the HomePage component in our StoreProvider . This makes it possible for the Homepage component to access the store, as we saw in the previous section.

Przebyliśmy długą drogę. Let's check what the app looks like, without any CSS.

App without CSS (Large preview)

Create Index.css

Delete the code in the index.css file and replace it with this:

 html { font-size: 14px; } body { margin: 0; padding: 0; font-size: 10px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .episode-layout { display: flex; flex-wrap: wrap; min-width: 100vh; } .episode-box { padding: .5rem; } .header { display: flex; justify-content: space-between; background: white; border-bottom: 1px solid black; padding: .5rem; position: sticky; top: 0; }

Our app now has a look and feel. Here's how it looks with CSS.

(duży podgląd)

Now we see that our episodes can finally be fetched and displayed, because we've adopted TypeScript all the way. Great, isn't it?

Add Favorite Episodes Feature

Let's add functionality that adds favorite episodes and that links it to a separate page. Let's go back to our Store component and add a few lines of code:

Note that the highlighted code is newly added:

 import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext<IState | any>(initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload }
 case 'ADD_FAV': return { ...state, favourites: [...state.favourites, action.payload] }
 default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return <Store.Provider value={{ state, dispatch }}>{children}</Store.Provider> }

To implement the “Add favorite” feature to our app, the ADD_FAV case is added. It returns an object that holds a copy of our previous state, as well as an array with a copy of the favorite state , with the payload .

We need an action that will be called each time a user clicks on the FAV button. Let's add the highlighted code to index.tx :

 import { IAction, IEpisode, Dispatch } from './types/interfaces'
export const fetchDataAction = async (dispatch: Dispatch) => { const URL = 'https://api.tvmaze.com/singlesearch/shows?q=la-casa-de-papel&embed=episodes' const data = await fetch(URL) const dataJSON = await data.json() return dispatch({ type: 'FETCH_DATA', payload: dataJSON._embedded.episodes }) }
export const toggleFavAction = (dispatch: any, episode: IEpisode | any): IAction => { let dispatchObj = { type: 'ADD_FAV', payload: episode } return dispatch(dispatchObj) }
export const toggleFavAction = (dispatch: any, episode: IEpisode | any): IAction => { let dispatchObj = { type: 'ADD_FAV', payload: episode } return dispatch(dispatchObj) }

We create a toggleFavAction function that takes dispatch and episodes as parameters, and any and IEpisode|any as their respective types, with IAction as our function type. We have an object whose type is ADD_FAV and that has episode as its payload. Lastly, we just return and dispatch the object.

Dodamy więcej fragmentów do EpisodeList.tsx . Skopiuj i wklej podświetlony kod:

 import React from 'react' import { IEpisode, IProps } from '../types/interfaces' const EpisodesList = (props: IProps): Array<JSX.Element> => {
 const { episodes, toggleFavAction, favourites, store } = props const { state, dispatch } = store

 return episodes.map((episode: IEpisode) => { return ( <section key={episode.id} className='episode-box'> <img src={!!episode.image ? episode.image.medium : ''} alt={`Money Heist - ${episode.name}`} /> <div>{episode.name}</div> <section style={{ display: 'flex', justifyContent: 'space-between' }}> <div> Seasion: {episode.season} Number: {episode.number} </div> <button type='button'
 onClick={() => toggleFavAction(state, dispatch, episode)} > {favourites.find((fav: IEpisode) => fav.id === episode.id) ? 'Unfav' : 'Fav'}
 </button> </section> </section> ) }) } export default EpisodesList

togglefavaction , favorites i store jako rekwizyty oraz destrukturyzujemy state , dispatch ze sklepu. Aby wybrać nasz ulubiony odcinek, dołączamy metodę toggleFavAction do zdarzenia onClick i przekazujemy właściwości state , dispatch i episode jako argumenty do funkcji.

Na koniec przechodzimy pętlą przez stan favorite , aby sprawdzić, czy fav.id (ulubiony identyfikator) pasuje do episode.id . Jeśli tak, przełączamy się między Unfav i Fav . Pomoże to użytkownikowi dowiedzieć się, czy umieścił ten odcinek w ulubionych, czy nie.

Zbliżamy się do końca. Ale nadal potrzebujemy strony, na której można połączyć ulubione odcinki, gdy użytkownik wybierze jeden z odcinków na stronie głównej.

Jeśli dotarłeś tak daleko, poklep się po plecach.

Komponent ulubionej strony

W folderze components utwórz plik FavPage.tsx . Skopiuj i wklej do niego następujący kod:

 import React, { lazy, Suspense } from 'react' import App from '../App' import { Store } from '../Store' import { IEpisodeProps } from '../types/interfaces' import { toggleFavAction } from '../Actions' const EpisodesList = lazy<any>(() => import('./EpisodesList')) export default function FavPage(): JSX.Element { const { state, dispatch } = React.useContext(Store) const props: IEpisodeProps = { episodes: state.favourites, store: { state, dispatch }, toggleFavAction, favourites: state.favourites } return ( <App> <Suspense fallback={<div>loading...</div>}> <div className='episode-layout'> <EpisodesList {...props} /> </div> </Suspense> </App> ) }

Aby stworzyć logikę wyboru ulubionych odcinków, napisaliśmy mały kod. Lazy i Suspense importujemy z lazy . Importujemy również Store , IEpisodeProps i toggleFavAction z ich odpowiednich plików.

Importujemy nasz komponent EpisodesList za pomocą funkcji React.lazy . Na koniec zwracamy komponent App . Wewnątrz używamy opakowania Suspense i ustawiamy rezerwę na div z tekstem ładującym.

Działa to podobnie do komponentu Homepage . Ten składnik uzyska dostęp do sklepu, aby uzyskać odcinki ulubione przez użytkownika. Następnie lista odcinków jest przekazywana do komponentu EpisodesList .

Dodajmy jeszcze kilka fragmentów kodu do pliku HomePage.tsx .

Dołącz toggleFavAction z ../Actions . Uwzględnij również metodę toggleFavAction jako rekwizyty.

 import React, { useContext, useEffect, lazy, Suspense } from 'react' import App from '../App' import { Store } from '../Store' import { IEpisodeProps } from '../types/interfaces'
import { fetchDataAction, toggleFavAction } from '../Actions'
const EpisodesList = lazy<any>(() => import('./EpisodesList')) const HomePage = (): JSX.Element => { const { state, dispatch } = useContext(Store) useEffect(() => { state.episodes.length === 0 && fetchDataAction(dispatch) }) const props: IEpisodeProps = { episodes: state.episodes, store: { state, dispatch },
 toggleFavAction, favourites: state.favourites
 } return ( <App> <Suspense fallback={<div>loading...</div>}> <section className='episode-layout'> <EpisodesList {...props} /> </section> </Suspense> </App> ) } export default HomePage

Nasza FavPage musi być połączona, więc potrzebujemy linku w naszym nagłówku w App.tsx . Aby to osiągnąć, używamy Reach Router, biblioteki podobnej do React Router. William Le wyjaśnia różnice między routerem Reach i routerem React.

W swoim CLI uruchom npm install @reach/router @types/reach__router . Instalujemy zarówno bibliotekę Reach Router, jak i typy reach-router .

Po udanej instalacji zaimportuj Link z @reach/router .

 import React, { useContext, Fragment } from 'react' import { Store } from './tsx'
import { Link } from '@reach/router'
 const App = ({ children }: { children: JSX.Element }): JSX.Element => {
 const { state } = useContext(Store)
return ( <Fragment> <header className='header'> <div> <h1>Money Heist</h1> <p>Pick your favourite episode</p> </div>
 <div> <Link to='/'>Home</Link> <Link to='/faves'>Favourite(s): {state.favourites.length}</Link> </div>
 </header> {children} </Fragment> ) } export default App

Destrukturyzujemy sklep z useContext . Wreszcie nasz dom będzie miał Link i ścieżkę do / , podczas gdy nasz ulubiony ma ścieżkę do /faves .

{state.favourites.length} sprawdza liczbę odcinków w ulubionych stanach i wyświetla ją.

Na koniec w naszym pliku index.tsx importujemy odpowiednio komponenty FavPage i HomePage i umieszczamy je w Router .

Skopiuj podświetlony kod do istniejącego kodu:

 import React from 'react' import ReactDOM from 'react-dom' import './index.css' import { StoreProvider } from './Store'
import { Router, RouteComponentProps } from '@reach/router' import HomePage from './components/HomePage' import FavPage from './components/FavPage' const RouterPage = ( props: { pageComponent: JSX.Element } & RouteComponentProps ) => props.pageComponent
ReactDOM.render( <StoreProvider>
 <Router> <RouterPage pageComponent={<HomePage />} path='/' /> <RouterPage pageComponent={<FavPage />} path='/faves' /> </Router>
 </StoreProvider>, document.getElementById('root') )

Zobaczmy teraz, jak działa zaimplementowany ADD_FAV .

Działa kod „Dodaj ulubione” (duży podgląd)

Usuń ulubioną funkcję

Na koniec dodamy funkcję „Usuń odcinek”, aby po kliknięciu przycisku przełączać się między dodawaniem lub usuwaniem ulubionego odcinka. W nagłówku wyświetlimy liczbę dodanych lub usuniętych odcinków.

SKLEP

Aby stworzyć funkcję „Usuń ulubiony odcinek”, dodamy kolejne etui w naszym sklepie. Przejdź więc do Store.tsx i dodaj podświetlony kod:

 import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext<IState | any>(initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload } case 'ADD_FAV': return { ...state, favourites: [...state.favourites, action.payload] }
 case 'REMOVE_FAV': return { ...state, favourites: action.payload }
 default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} } default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} } default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} }

Dodajemy jeszcze jeden przypadek o nazwie REMOVE_FAV i zwracamy obiekt zawierający kopię naszego initialState . Ponadto stan favorites zawiera ładunek akcji.

AKCJA

Skopiuj następujący podświetlony kod i wklej go w action.ts :

 import { IAction, IEpisode, IState, Dispatch } from './types/interfaces'
export const fetchDataAction = async (dispatch: Dispatch) => { const URL = 'https://api.tvmaze.com/singlesearch/shows?q=la-casa-de-papel&embed=episodes' const data = await fetch(URL) const dataJSON = await data.json() return dispatch({ type: 'FETCH_DATA', payload: dataJSON.\_embedded.episodes }) } //Add IState withits type
export const toggleFavAction = (state: IState, dispatch: any, episode: IEpisode | any): IAction => { const episodeInFav = state.favourites.includes(episode)
 let dispatchObj = { type: 'ADD_FAV', payload: episode }
 if (episodeInFav) { const favWithoutEpisode = state.favourites.filter( (fav: IEpisode) => fav.id !== episode.id ) dispatchObj = { type: 'REMOVE_FAV', payload: favWithoutEpisode }
 } return dispatch(dispatchObj) }

Interfejs IState z ./types/interfaces , ponieważ będziemy musieli przekazać go jako typ do właściwości state w funkcji toggleFavAction .

Zmienna episodeInFav jest tworzona w celu sprawdzenia, czy istnieje odcinek, który istnieje w stanie favorites .

Filtrujemy stan ulubionych, aby sprawdzić, czy identyfikator ulubionego nie jest równy identyfikatorowi odcinka. W ten sposób do obiektu dispatchObj zostaje ponownie przypisany typ REMOVE_FAV i ładunek favWithoutEpisode .

Zobaczmy podgląd wyników naszej aplikacji.

Wniosek

W tym artykule zobaczyliśmy, jak skonfigurować TypeScript w projekcie React i jak przenieść projekt z vanilla React do TypeScript.

Zbudowaliśmy również aplikację z TypeScript i React, aby zobaczyć, jak TypeScript jest używany w projektach React. Ufam, że nauczyłeś się kilku rzeczy.

Podziel się swoją opinią i doświadczeniami z TypeScript w sekcji komentarzy poniżej. Chętnie zobaczę, co wymyślisz!

Obsługiwane repozytorium dla tego artykułu jest dostępne w serwisie GitHub.

Bibliografia

  1. „Jak przenieść aplikację React do TypeScript”, Joe Previte
  2. „Dlaczego i jak używać TypeScript w aplikacji React?”, Mahesh Haldar