Ustawianie TypeScriptu dla nowoczesnych projektów React za pomocą Webpack
Opublikowany: 2022-03-10W 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).
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).
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.
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
:
- Dodaj
webpack.config.js
, aby dodać konfiguracje związane z webpackiem. - Dodaj
tsconfig.json
dla wszystkich naszych konfiguracji TypeScript. - Dodaj nowy katalog,
src
. - Utwórz nowy katalog,
components
, w folderzesrc
. - Na koniec dodaj
index.html
,App.tsx
iindex.tsx
w folderzecomponents
.
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życiees2015
pozwala na użycie składni ECMAScript 6). -
module
Generuje kod modułu. -
noImplicitAny
Zgłasza błędy dla deklaracji z domniemanymany
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ę tobundle.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
.
- każdy plik, który kończy się rozszerzeniem
-
plugins
Webpack ma swoje ograniczenia i udostępnia wtyczki, które je przezwyciężają i rozszerzają jego możliwości. Na przykładhtml-webpack-plugin
tworzy plik szablonu, który jest renderowany w przeglądarce z plikuindex.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:
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:
- Dodaj TypeScript i typy.
- Dodaj
tsconfig.json
. - Zacznij od małych.
- 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 domniemanymany
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:
- Zmień rozszerzenie pliku na
.tsx
. - 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:
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
.
- Utwórz odpowiednie typy i interfejsy dla naszych odcinków w
- Dodaj odcinki.
- Skonfiguruj sklep, aby dodać odcinki w
store.tsx
. - Utwórz akcję dodawania odcinków w
action.ts
.
- Skonfiguruj sklep, aby dodać odcinki w
- Usuń odcinki.
- Skonfiguruj sklep do usuwania odcinków w
store.tsx
. - Utwórz akcję usuwania odcinków w
action.ts
.
- Skonfiguruj sklep do usuwania odcinków w
- Ulubiony odcinek.
- Importuj komponent
EpisodesList
do ulubionego odcinka. - Renderuj
EpisodesList
w ulubionym odcinku.
- Importuj komponent
- 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
.
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 APIcreateContext
z Reacta, dlatego go zaimportowaliśmy. - Zaimportowaliśmy
IState
iIAction
z./types/interfaces
. - Zadeklarowaliśmy obiekt
initialState
z typemIState
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śmystate
iaction
jako parametry. Funkcjareducer
ma instrukcję switch, która sprawdza wartośćaction.type
. Jeśli wartością jestFETCH_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 funkcjiStorePrivder
zadeklarowaliśmy hakuseReducer
. - Zdestrukturyzowaliśmy
state
idispatch
. - Aby nasz sklep był dostępny dla wszystkich komponentów, przekazaliśmy wartość obiektu zawierającą
state
idispatch
.
state
zawierający nasze odcinki i stan ulubionych zostanie udostępniony przez inne komponenty, natomiast dispatch
jest funkcją zmieniającą stan.
- Wyeksportujemy
Store
iStoreProvider
, 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ścidispatch
jako parametr. - Ponieważ nasza funkcja jest asynchroniczna,
async
iawait
. - 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łaniedata.json()
. - Na koniec zwracamy funkcję wysyłającą, która ma właściwość
type
i ciągFETCH_DATA
. Posiada równieżpayload()
._embedded.episodes
to tablica obiektów epizodów z naszegoendpoint
.
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
iIProps
zinterfaces.tsx
. - Następnie tworzymy funkcję
EpisodesList
, która pobiera rekwizyty. Rekwizyty będą miały typIProps
, podczas gdy funkcja ma typArray<JSX.Element>
.
Visual Studio Code sugeruje, aby nasz typ funkcji był zapisany jako JSX.Element[]
.
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
zprops
, których typem jestIEpisode
.
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 jestepisode.id
, orazclassName
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
lubepisode.image.medium
. W przeciwnym razie wyświetlamy pusty ciąg, jeśli nie zostanie znaleziony żaden obraz. Ponadto dołączyliśmyepisode.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
iSuspense
z Reacta. Importowany składnik aplikacji jest podstawą, na której wszystkie inne składniki muszą otrzymać wartość sklepu. - Importujemy również
Store
,IEpisodeProps
iFetchDataAction
z ich odpowiednich plików. - Importujemy komponent
EpisodesList
za pomocą funkcjiReact.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
idispatch
jako rekwizyty zeStore
. - Znak ampersand (&&) w haczyku
useEffect
sprawdza, czy nasz stan epizodów jestempty
(lub równy 0). W przeciwnym razie zwracamy funkcjęfetchDataAction
. - Na koniec zwracamy komponent
App
. Wewnątrz używamy opakowaniaSuspense
i ustawiamyfallback
na div z tekstemloading
. 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.
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.
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
.
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
- „Jak przenieść aplikację React do TypeScript”, Joe Previte
- „Dlaczego i jak używać TypeScript w aplikacji React?”, Mahesh Haldar