Impostazione di TypeScript per progetti React moderni utilizzando Webpack
Pubblicato: 2022-03-10In questa era di sviluppo software, JavaScript può essere utilizzato per sviluppare quasi tutti i tipi di app. Tuttavia, il fatto che JavaScript sia digitato dinamicamente potrebbe essere un problema per la maggior parte delle grandi aziende, a causa della sua funzionalità di controllo del tipo allentata.
Fortunatamente, non dobbiamo aspettare che l'Ecma Technical Committee 39 introduca un sistema di tipi statici in JavaScript. Possiamo invece usare TypeScript.
JavaScript, essendo digitato dinamicamente, non è a conoscenza del tipo di dati di una variabile finché tale variabile non viene istanziata in fase di esecuzione. Gli sviluppatori che scrivono programmi software di grandi dimensioni potrebbero avere la tendenza a riassegnare una variabile, dichiarata in precedenza, a un valore di tipo diverso, senza alcun avviso o problema di sorta, con conseguente bug spesso trascurato.
In questo tutorial impareremo cos'è TypeScript e come lavorarci in un progetto React. Alla fine, avremo creato un progetto composto da un'app per la selezione di episodi per il programma TV Money Heist , utilizzando TypeScript e gli attuali hook simili a React ( useState , useEffect , useReducer , useContext ). Con questa conoscenza, puoi continuare a sperimentare TypeScript nei tuoi progetti.
Questo articolo non è un'introduzione a TypeScript. Quindi, non esamineremo la sintassi di base di TypeScript e JavaScript. Tuttavia, non devi essere un esperto in nessuna di queste lingue per seguire, perché cercheremo di seguire il principio KISS (mantienilo semplice, stupido).
Che cos'è TypeScript?
Nel 2019, TypeScript è stata classificata come la settima lingua più utilizzata e la quinta lingua in più rapida crescita su GitHub. Ma cos'è esattamente TypeScript?
Secondo la documentazione ufficiale, TypeScript è un superset tipizzato di JavaScript che viene compilato in JavaScript semplice. È sviluppato e gestito da Microsoft e dalla comunità open source.
"Superset" in questo contesto significa che il linguaggio contiene tutte le caratteristiche e le funzionalità di JavaScript e poi alcune. TypeScript è un linguaggio di scripting tipizzato.
Offre agli sviluppatori un maggiore controllo sulla propria base di codice tramite l'annotazione del tipo, le classi e l'interfaccia, evitando agli sviluppatori di dover correggere manualmente fastidiosi bug nella console.
TypeScript non è stato creato per alterare JavaScript. Invece, espande JavaScript con nuove preziose funzionalità. Qualsiasi programma scritto in JavaScript semplice verrà eseguito anche come previsto in TypeScript, comprese le app mobili multipiattaforma e i back-end in Node.js.
Ciò significa che puoi anche scrivere app React in TypeScript, come faremo in questo tutorial.
Perché dattiloscritto?
Forse non sei convinto di abbracciare la bontà di TypeScript. Consideriamo alcuni dei suoi vantaggi.
Meno bug
Non possiamo eliminare tutti i bug nel nostro codice, ma possiamo ridurli. TypeScript controlla i tipi in fase di compilazione e genera errori se il tipo di variabile cambia.
Essere in grado di trovare questi errori evidenti ma frequenti così presto rende molto più semplice gestire il codice con i tipi.
Il refactoring è più facile
Probabilmente vuoi spesso rifattorizzare un bel po' di cose, ma poiché toccano così tanto altro codice e molti altri file, sei cauto nel modificarli.
In TypeScript, queste cose possono spesso essere rifattorizzato con un semplice clic del comando "Rinomina simbolo" nel tuo ambiente di sviluppo integrato (IDE).

In un linguaggio tipizzato dinamicamente come JavaScript, l'unico modo per refactoring di più file contemporaneamente è con la tradizionale funzione "cerca e sostituisci" utilizzando espressioni regolari (RegExp).
In un linguaggio digitato staticamente come TypeScript, "cerca e sostituisci" non è più necessario. Con i comandi IDE come "Trova tutte le occorrenze" e "Rinomina simbolo", puoi vedere tutte le occorrenze nell'app della funzione, classe o proprietà specificata di un'interfaccia oggetto.
TypeScript ti aiuterà a trovare tutte le istanze del bit rifattorizzato, rinominarlo e avvisarti con un errore di compilazione nel caso in cui il tuo codice abbia dei tipi non corrispondenti dopo il refactoring.
TypeScript ha ancora più vantaggi di quelli che abbiamo trattato qui.
Svantaggi di TypeScript
TypeScript non è sicuramente privo di svantaggi, anche date le promettenti funzionalità evidenziate sopra.
Un falso senso di sicurezza
La funzione di controllo del tipo di TypeScript crea spesso un falso senso di sicurezza tra gli sviluppatori. Il controllo del tipo ci avverte infatti quando qualcosa non va nel nostro codice. Tuttavia, i tipi statici non riducono la densità complessiva dei bug.
Pertanto, la forza del tuo programma dipenderà dal tuo utilizzo di TypeScript, perché i tipi sono scritti dallo sviluppatore e non controllati in fase di esecuzione.
Se stai cercando TypeScript per ridurre i tuoi bug, considera invece lo sviluppo basato su test.
Sistema di digitazione complicato
Il sistema di digitazione, sebbene sia un ottimo strumento sotto molti aspetti, a volte può essere un po' complicato. Questo svantaggio deriva dal fatto che è completamente interoperabile con JavaScript, il che lascia ancora più spazio alle complicazioni.
Tuttavia, TypeScript è ancora JavaScript, quindi è importante comprendere JavaScript.
Quando usare TypeScript?
Ti consiglierei di usare TypeScript nei seguenti casi:
- Se stai cercando di creare un'applicazione che verrà mantenuta per un lungo periodo , ti consiglio vivamente di iniziare con TypeScript, perché promuove l'autodocumentazione del codice, aiutando così gli altri sviluppatori a capire facilmente il tuo codice quando si uniscono alla tua base di codice .
- Se devi creare una libreria , considera di scriverla in TypeScript. Aiuterà gli editor di codice a suggerire i tipi appropriati agli sviluppatori che stanno usando la tua libreria.
Nelle ultime sezioni, abbiamo bilanciato i pro ei contro di TypeScript. Passiamo all'attività del giorno: impostare TypeScript in un moderno progetto React .
Iniziare
Esistono diversi modi per impostare TypeScript in un progetto React. In questo tutorial, ne tratteremo solo due.
Metodo 1: crea l'app React + TypeScript
Circa due anni fa, il team React ha rilasciato Create React App 2.1, con supporto TypeScript. Quindi, potresti non dover mai fare alcun lavoro pesante per inserire TypeScript nel tuo progetto.

Per avviare un nuovo progetto Create React App, puoi eseguire questo...
npx create-react-app my-app --folder-name… o questo:
yarn create react-app my-app --folder-name Per aggiungere TypeScript a un progetto Create React App, prima installarlo e i rispettivi @types :
npm install --save typescript @types/node @types/react @types/react-dom @types/jest… o:
yarn add typescript @types/node @types/react @types/react-dom @types/jest Quindi, rinomina i file (ad esempio, index.js in index.tsx ) e riavvia il server di sviluppo !
È stato veloce, vero?
Metodo 2: imposta TypeScript con Webpack
Webpack è un bundler di moduli statici per applicazioni JavaScript. Prende tutto il codice dalla tua applicazione e lo rende utilizzabile in un browser web. I moduli sono blocchi di codice riutilizzabili creati da JavaScript, node_modules , immagini e stili CSS della tua app, che sono pacchettizzati per essere facilmente utilizzati sul tuo sito web.
Crea un nuovo progetto
Iniziamo creando una nuova directory per il nostro progetto:
mkdir react-webpack cd react-webpackUseremo npm per inizializzare il nostro progetto:
npm init -y Il comando precedente genererà un file package.json con alcuni valori predefiniti. Aggiungiamo anche alcune dipendenze per webpack, TypeScript e alcuni moduli specifici di React.
Installazione di pacchetti
Infine, dovremmo installare i pacchetti necessari. Apri la tua interfaccia della riga di comando (CLI) ed esegui questo:
#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 Aggiungiamo anche manualmente alcuni file e cartelle diversi nella nostra cartella react-webpack :
- Aggiungi
webpack.config.jsper aggiungere configurazioni relative al webpack. - Aggiungi
tsconfig.jsonper tutte le nostre configurazioni TypeScript. - Aggiungi una nuova directory,
src. - Crea una nuova directory,
components, nella cartellasrc. - Infine, aggiungi
index.html,App.tsxeindex.tsxnella cartella deicomponents.
Struttura del progetto
Pertanto, la nostra struttura di cartelle sarà simile a questa:
├── package.json ├── package-lock.json ├── tsconfig.json ├── webpack.config.js ├── .gitignore └── src └──components ├── App.tsx ├── index.tsx ├── index.htmlInizia ad aggiungere del codice
Inizieremo con 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> Questo creerà l'HTML, con un div vuoto con un ID di output .
Aggiungiamo il codice al nostro componente 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> ); Abbiamo creato un oggetto interfaccia e lo abbiamo chiamato HelloWorldProps , con userName e lang aventi un tipo di string .
Abbiamo passato gli props di scena al nostro componente App e l'abbiamo esportato.
Ora, aggiorniamo il codice in 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") ); Abbiamo appena importato il componente App in index.tsx . Quando webpack vede qualsiasi file con l'estensione .ts o .tsx , trasporterà quel file usando la libreria awesome-typescript-loader.
Configurazione dattiloscritta
Aggiungeremo quindi alcune configurazioni a tsconfig.json :
{ "compilerOptions": { "jsx": "react", "module": "commonjs", "noImplicitAny": true, "outDir": "./build/", "preserveConstEnums": true, "removeComments": true, "sourceMap": true, "target": "es5" }, "include": [ "src/components/index.tsx" ] } Diamo anche un'occhiata alle diverse opzioni che abbiamo aggiunto a tsconfig.json :
-
compilerOptionsRappresenta le diverse opzioni del compilatore. -
jsx:reactAggiunge il supporto per JSX nei file.tsx. -
libAggiunge un elenco di file di libreria alla compilation (ad esempio, l'utilizzo dies2015consente di utilizzare la sintassi ECMAScript 6). -
moduleGenera il codice del modulo. -
noImplicitAnySolleva errori per dichiarazioni con unanytipo implicito. -
outDirRappresenta la directory di output. -
sourceMapGenera un file.map, che può essere molto utile per il debug dell'app. -
targetRappresenta la versione ECMAScript di destinazione in cui transpilare il nostro codice (possiamo aggiungere una versione in base ai nostri requisiti specifici del browser). -
includeUtilizzato per specificare l'elenco di file da includere.
Configurazione del pacchetto web
Aggiungiamo alcune configurazioni di webpack a 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", }), ], }; Diamo un'occhiata alle diverse opzioni che abbiamo aggiunto a webpack.config.js :
-
entryQuesto specifica il punto di ingresso per la nostra app. Potrebbe essere un singolo file o una matrice di file che vogliamo includere nella nostra build. -
outputContiene la configurazione dell'output. L'app lo esamina quando si tenta di inviare il codice in bundle dal nostro progetto al disco. Il percorso rappresenta la directory di output in cui inviare il codice e il nome file rappresenta il nome file dello stesso. È generalmente chiamatobundle.js. -
resolveWebpack esamina questo attributo per decidere se raggruppare o saltare il file. Pertanto, nel nostro progetto, webpack considererà i file con le estensioni.js,.jsx,.json,.tse.tsxper il raggruppamento. -
modulePossiamo abilitare il webpack per caricare un file particolare quando richiesto dall'app, utilizzando i caricatori. Prende un oggetto rules che specifica che:- qualsiasi file che termina con l'estensione
.tsxo.tsdovrebbe utilizzareawesome-typescript-loaderdi caratteri impressionante per essere caricato; - i file che terminano con l'estensione
.jsdevono essere caricati consource-map-loader; - i file che terminano con l'estensione
.cssdevono essere caricati concss-loader.
- qualsiasi file che termina con l'estensione
-
pluginsWebpack ha i suoi limiti e fornisce plugin per superarli ed estendere le sue capacità. Ad esempio,html-webpack-plugincrea un file modello di cui viene eseguito il rendering nel browser dal fileindex.htmlnella directory./src/component/index.html.
MiniCssExtractPlugin il rendering del file CSS padre dell'app.
Aggiunta di script a package.json
Possiamo aggiungere diversi script per creare app React nel nostro file package.json :
"scripts": { "start": "webpack-dev-server --open", "build": "webpack" }, Ora, esegui npm start nella tua CLI. Se è andato tutto bene, dovresti vedere questo:

Se hai un talento per il webpack, clona il repository per questa configurazione e usalo nei tuoi progetti.
Creazione di file
Crea una cartella src e un file index.tsx . Questo sarà il file di base che esegue il rendering di React.
Ora, se eseguiamo npm start , eseguirà il nostro server e aprirà una nuova scheda. L'esecuzione npm run build creerà il webpack per la produzione e creerà una cartella di build per noi.
Abbiamo visto come configurare TypeScript da zero utilizzando l'app Create React e il metodo di configurazione del webpack.
Uno dei modi più rapidi per ottenere una conoscenza completa di TypeScript è convertire uno dei tuoi progetti vanilla React esistenti in TypeScript. Sfortunatamente, l'adozione incrementale di TypeScript in un progetto Vanilla React esistente è stressante perché comporta la necessità di espellere o rinominare tutti i file, il che comporterebbe conflitti e una richiesta pull gigante se il progetto appartenesse a un team di grandi dimensioni.
Successivamente, vedremo come migrare facilmente un progetto React in TypeScript.
Migrare un'app Create React esistente in TypeScript
Per rendere questo processo più gestibile, lo suddivideremo in passaggi, che ci consentiranno di migrare in singoli blocchi. Ecco i passaggi che adotteremo per migrare il nostro progetto:
- Aggiungi TypeScript e tipi.
- Aggiungi
tsconfig.json. - Inizia in piccolo.
- Rinomina l'estensione dei file in
.tsx.
1. Aggiungi TypeScript al progetto
Innanzitutto, dovremo aggiungere TypeScript al nostro progetto. Supponendo che il tuo progetto React sia stato avviato con Create React App, possiamo eseguire quanto segue:
# 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 Si noti che non abbiamo ancora cambiato nulla in TypeScript. Se eseguiamo il comando per avviare il progetto in locale ( npm start o yarn start ), non cambia nulla. Se questo è il caso, allora fantastico! Siamo pronti per il prossimo passo.
2. Aggiungi il file tsconfig.json
Prima di sfruttare TypeScript, dobbiamo configurarlo tramite il file tsconfig.json . Il modo più semplice per iniziare è impalcarne uno usando questo comando:
npx tsc --init Questo ci fornisce alcune nozioni di base, con molto codice commentato. Ora, sostituisci tutto il codice in tsconfig.json con questo:
{ "compilerOptions": { "jsx": "react", "module": "commonjs", "noImplicitAny": true, "outDir": "./build/", "preserveConstEnums": true, "removeComments": true, "sourceMap": true, "target": "es5" }, "include": [ "./src/**/**/\*" ] }Configurazione dattiloscritta
Diamo anche un'occhiata alle diverse opzioni che abbiamo aggiunto a tsconfig.json :
-
compilerOptionsRappresenta le diverse opzioni del compilatore.-
targetTraduce i costrutti JavaScript più recenti in una versione precedente, come ECMAScript 5. -
libAggiunge un elenco di file di libreria alla compilation (ad esempio, l'utilizzo di es2015 consente di utilizzare la sintassi ECMAScript 6). -
jsx:reactAggiunge il supporto per JSX nei file.tsx. -
libAggiunge un elenco di file di libreria alla compilation (ad esempio, l'utilizzo di es2015 consente di utilizzare la sintassi ECMAScript 6). -
moduleGenera il codice del modulo. -
noImplicitAnyUtilizzato per generare errori per dichiarazioni con un tipo implicitoany. -
outDirRappresenta la directory di output. -
sourceMapGenera un file.map, che può essere molto utile per il debug della nostra app. -
includeUtilizzato per specificare l'elenco di file da includere.
-
Le opzioni di configurazione variano in base alla richiesta di un progetto. Potrebbe essere necessario controllare il foglio di calcolo delle opzioni di TypeScript per capire cosa si adatta al tuo progetto.
Abbiamo intrapreso solo le azioni necessarie per preparare le cose. Il nostro prossimo passo è migrare un file in TypeScript.
3. Inizia con un componente semplice
Approfitta della capacità di TypeScript di essere adottato gradualmente. Vai un file alla volta al tuo ritmo. Fai ciò che ha senso per te e il tuo team. Non cercare di affrontarlo tutto in una volta.
Per convertirlo correttamente, dobbiamo fare due cose:
- Cambia l'estensione del file in
.tsx. - Aggiungi l'annotazione del tipo (che richiederebbe una certa conoscenza di TypeScript).
4. Rinominare le estensioni dei file in .tsx
In una grande base di codice, potrebbe sembrare faticoso rinominare i file singolarmente.
Rinomina più file su macOS
Rinominare più file può essere una perdita di tempo. Ecco come puoi farlo su un Mac. Fai clic con il pulsante destro del mouse (o Ctrl + clic, oppure fai clic con due dita contemporaneamente sul trackpad se stai utilizzando un MacBook) sulla cartella che contiene i file che desideri rinominare. Quindi, fai clic su "Rivela nel Finder". Nel Finder, seleziona tutti i file che desideri rinominare. Fai clic con il pulsante destro del mouse sui file selezionati e scegli "Rinomina X elementi..." Quindi vedrai qualcosa del genere:

Inserisci la stringa che vuoi trovare e la stringa con cui vuoi sostituire quella stringa trovata e premi "Rinomina". Fatto.
Rinomina più file su Windows
La ridenominazione di più file su Windows va oltre lo scopo di questo tutorial, ma è disponibile una guida completa. Di solito si ottengono errori dopo aver rinominato i file; devi solo aggiungere le annotazioni del tipo. Puoi rispolverare questo nella documentazione.
Abbiamo spiegato come configurare TypeScript in un'app React. Ora creiamo un'app per la selezione di episodi per Money Heist usando TypeScript.
Non tratteremo i tipi di base di TypeScript. È necessario esaminare la documentazione prima di continuare in questo tutorial.
Tempo per costruire
Per rendere questo processo meno scoraggiante, lo suddivideremo in passaggi, che ci consentiranno di creare l'app in singoli blocchi. Ecco tutti i passaggi che adotteremo per creare il selettore di episodi di Money Heist :
- Impalcatura di un'app Create React.
- Recupera episodi.
- Crea i tipi e le interfacce appropriati per i nostri episodi in
interface.ts. - Configura il negozio per il recupero degli episodi in
store.tsx. - Crea l'azione per il recupero degli episodi in
action.ts. - Crea un componente
EpisodeList.tsxche contenga gli episodi recuperati. - Importa il componente
EpisodesListnella nostra home page usandoReact Lazy and Suspense.
- Crea i tipi e le interfacce appropriati per i nostri episodi in
- Aggiungi episodi.
- Configura store per aggiungere episodi in
store.tsx. - Crea l'azione per aggiungere episodi in
action.ts.
- Configura store per aggiungere episodi in
- Rimuovi episodi.
- Configura lo store per eliminare gli episodi in
store.tsx. - Crea l'azione per eliminare gli episodi in
action.ts.
- Configura lo store per eliminare gli episodi in
- Episodio preferito.
- Importa il componente
EpisodesListnell'episodio preferito. - Render
EpisodesListall'interno dell'episodio preferito.
- Importa il componente
- Utilizzo di Reach Router per la navigazione.
Imposta Reagire
Il modo più semplice per configurare React è utilizzare l'app Crea React. Create React App è un modo ufficialmente supportato per creare applicazioni React a pagina singola. Offre una configurazione di build moderna senza configurazione.
Ne faremo uso per avviare l'applicazione che andremo a costruire. Dalla tua CLI, esegui il comando seguente:
npx create-react-app react-ts-app && cd react-ts-app Una volta completata l'installazione, avviare il server React eseguendo npm start .

Comprensione di interfacce e tipi in dattiloscritto
Le interfacce in TypeScript vengono utilizzate quando è necessario assegnare tipi alle proprietà degli oggetti. Quindi, useremmo le interfacce per definire i nostri tipi.
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 } Durante la compilazione del codice sopra, vedremmo questo errore: “I tipi di salary di proprietà sono incompatibili. La string di tipo non è assegnabile al number di tipo."
Tali errori si verificano in TypeScript quando a una proprietà oa una variabile viene assegnato un tipo diverso dal tipo definito. In particolare, lo snippet sopra indica che alla proprietà salary è stato assegnato un tipo string anziché un tipo number .
Creiamo un file interface.ts nella nostra cartella src . Copia e incolla questo codice al suo interno:
/** |-------------------------------------------------- | 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> }È buona norma aggiungere una "I" al nome dell'interfaccia. Rende il codice leggibile. Tuttavia, potresti decidere di escluderlo.
Interfaccia IEpisode
La nostra API restituisce un insieme di proprietà come airdate , airstamp , airtime , id , image , name , number , runtime , season , summary e url . Quindi, abbiamo definito un'interfaccia IEpisode e impostato i tipi di dati appropriati sulle proprietà dell'oggetto.
Interfaccia IState
La nostra interfaccia IState ha rispettivamente le proprietà degli episodes e favorites e un'interfaccia Array<IEpisode> .
Iazione
Le proprietà dell'interfaccia IAction sono payload e type . La proprietà type ha un tipo stringa, mentre il payload ha un tipo Array | any Array | any .
Si noti che Array | any Array | any significa un array dell'interfaccia dell'episodio o qualsiasi tipo.
Il tipo di Dispatch è impostato su React.Dispatch e un'interfaccia <IAction> . Si noti che React.Dispatch è il tipo standard per la funzione di dispatch , in base alla base di codice @types/react , mentre <IAction> è un array dell'azione Interface.
Inoltre, Visual Studio Code ha un correttore TypeScript. Quindi, semplicemente evidenziando o passando sopra il codice, è abbastanza intelligente suggerire il tipo appropriato.
In altre parole, per poter utilizzare la nostra interfaccia nelle nostre app, dobbiamo esportarla. Finora, abbiamo il nostro negozio e le nostre interfacce che contengono il tipo del nostro oggetto. Creiamo ora il nostro negozio. Si noti che le altre interfacce seguono le stesse convenzioni di quelle spiegate.

Recupera episodi
Creazione di un negozio
Per recuperare i nostri episodi, abbiamo bisogno di un archivio che contenga lo stato iniziale dei dati e che definisca la nostra funzione di riduzione.
Useremo useReducer hook per configurarlo. Crea un file store.tsx nella tua cartella src . Copia e incolla il codice seguente al suo interno.
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} }
Di seguito sono riportati i passaggi che abbiamo adottato per creare il negozio:
- Per definire il nostro negozio, abbiamo bisogno
useReducere dell'APIcreateContextdi React, motivo per cui l'abbiamo importato. - Abbiamo importato
IStateeIActionda./types/interfaces. - Abbiamo dichiarato un oggetto
initialStatecon un tipoIStatee le proprietà degli episodi e dei preferiti, che sono entrambi impostati rispettivamente su un array vuoto. - Successivamente, abbiamo creato una variabile
Storeche contiene il metodocreateContexte a cui viene passatoinitialState.
Il tipo di metodo createContext è <IState | any> <IState | any> , il che significa che potrebbe essere un tipo di <IState> o any . Vedremo any tipo usato spesso in questo articolo.
- Successivamente, abbiamo dichiarato una funzione
reducere passatostateeactioncome parametri. La funzionereducerha un'istruzione switch che controlla il valore diaction.type. Se il valore èFETCH_DATA, restituisce un oggetto che ha una copia del nostro stato(...state)e dello stato dell'episodio che contiene il nostro payload dell'azione. - Nell'istruzione switch, restituiamo uno stato di
default.
Si noti che i parametri di state e action nella funzione riduttore hanno rispettivamente i tipi IState e IAction . Inoltre, la funzione reducer ha un tipo di IState .
- Infine, abbiamo dichiarato una funzione
StoreProvider. Ciò consentirà a tutti i componenti della nostra app di accedere allo store. - Questa funzione prende
childrencome supporto e all'interno della funzioneStorePrivder, abbiamo dichiarato l'hookuseReducer. - Abbiamo destrutturato lo
statee ladispatch. - Per rendere il nostro negozio accessibile a tutti i componenti, abbiamo passato un valore oggetto contenente
stateedispatch.
Lo state che contiene i nostri episodi e lo stato dei preferiti sarà reso accessibile da altri componenti, mentre l' dispatch è una funzione che cambia lo stato.
- Esporteremo
StoreeStoreProvider, in modo che possa essere utilizzato nella nostra applicazione.
Crea Action.ts
Dovremo fare richieste all'API per recuperare gli episodi che verranno mostrati all'utente. Questo verrà fatto in un file di azione. Crea un file Action.ts , quindi incolla il codice seguente:
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 }) }Innanzitutto, dobbiamo importare le nostre interfacce in modo che possano essere utilizzate in questo file. Per creare l'azione sono stati eseguiti i seguenti passaggi:
- La funzione
fetchDataActionaccetta i prop didispatchcome parametro. - Poiché la nostra funzione è asincrona, utilizzeremo
asynceawait. - Creiamo una variabile(
URL) che contiene il nostro endpoint API. - Abbiamo un'altra variabile denominata
datache contiene la risposta dall'API. - Quindi, memorizziamo la risposta JSON in
dataJSON, dopo aver ottenuto la risposta in formato JSON chiamandodata.json(). - Infine, restituiamo una funzione di invio che ha una proprietà di
typee una stringa diFETCH_DATA. Ha anche unpayload()._embedded.episodesè l'array dell'oggetto episodi dal nostroendpoint.
Si noti che la funzione fetchDataAction recupera il nostro endpoint, lo converte in oggetti JSON e restituisce la funzione di invio, che aggiorna lo stato dichiarato in precedenza nello Store.
Il tipo di spedizione esportato è impostato su React.Dispatch . Si noti che React.Dispatch è il tipo standard per la funzione di invio in base alla base di codice @types/react , mentre <IAction> è un array dell'azione dell'interfaccia.
Componente elenco episodi
Per mantenere la riutilizzabilità della nostra app, manterremo tutti gli episodi recuperati in un file separato, quindi importeremo il file nel nostro componente homePage .
Nella cartella dei components , crea un file EpisodesList.tsx e copia e incolla il codice seguente:
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- Importiamo
IEpisodeeIPropsdainterfaces.tsx. - Successivamente, creiamo una funzione
EpisodesListche accetta oggetti di scena. Gli oggetti di scena avranno un tipo diIProps, mentre la funzione ha un tipo diArray<JSX.Element>.
Visual Studio Code suggerisce che il nostro tipo di funzione sia scritto come JSX.Element[] .

Mentre Array<JSX.Element> è uguale a JSX.Element[] , Array<JSX.Element> è chiamato identità generica. Pertanto, il modello generico verrà utilizzato spesso in questo articolo.
- All'interno della funzione destrutturiamo gli
episodesdaprops, che ha come tipoIEpisode.
Leggi l'identità generica, questa conoscenza sarà necessaria mentre procediamo.
- Abbiamo restituito gli oggetti di scena degli
episodese li abbiamo mappati per restituire alcuni tag HTML. - La prima sezione contiene la
key, che èepisode.id, e unclassNamedi Episodeepisode-box, che verrà creato in seguito. Sappiamo che i nostri episodi hanno immagini; quindi, il tag immagine. - L'immagine ha un operatore ternario che controlla se è presente un
episode.imageo unepisode.image.medium. Altrimenti, visualizziamo una stringa vuota se non viene trovata alcuna immagine. Inoltre, abbiamo incluso l'episode.namein un div.
Nella section , mostriamo la stagione a cui appartiene un episodio e il suo numero. Abbiamo un pulsante con la scritta Fav . Abbiamo esportato il componente EpisodesList in modo da poterlo utilizzare nella nostra app.
Componente della pagina iniziale
Vogliamo che la home page attivi la chiamata API e visualizzi gli episodi utilizzando il componente EpisodesList che abbiamo creato. All'interno della cartella dei components , crea il componente HomePage e copia e incolla il codice seguente su di esso:
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- Importiamo
useContext,useEffect,lazyeSuspenseda React. Il componente dell'app importato è la base su cui tutti gli altri componenti devono ricevere il valore del negozio. - Importiamo anche
Store,IEpisodePropseFetchDataActiondai rispettivi file. - Importiamo il componente
EpisodesListutilizzando la funzioneReact.lazydisponibile in React 16.6.
React lazy loading supporta la convenzione di suddivisione del codice. Pertanto, il nostro componente EpisodesList viene caricato dinamicamente, invece di essere caricato in una volta, migliorando così le prestazioni della nostra app.
- Destrutturiamo lo
stateedispatchcome oggetti di scena dalStore. - La e commerciale (&&)
useEffectcontrolla se lo stato degli episodi èempty(o uguale a 0). Altrimenti, restituiamo la funzionefetchDataAction. - Infine, restituiamo il componente
App. Al suo interno, utilizziamo il wrapperSuspensee impostiamo ilfallbacksu un div con il testo diloading. Questo verrà mostrato all'utente mentre attendiamo la risposta dall'API. - Il componente
EpisodesListsi attiverà quando i dati saranno disponibili e i dati che conterranno gliepisodessono ciò che ci diffondiamo.
Imposta Index.txs
Il componente Homepage deve essere figlio di StoreProvider . Dovremo farlo nel file di index . Rinomina index.js in index.tsx e incolla il codice seguente:
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') ) Importiamo StoreProvider , HomePage e index.css dai rispettivi file. 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.
Abbiamo fatto molta strada. 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.
Aggiungeremo altri frammenti a EpisodeList.tsx . Copia e incolla il codice evidenziato:
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 } = storereturn 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 e store come oggetti di scena e destrutturiamo state , una dispatch dal negozio. Per selezionare il nostro episodio preferito, includiamo il metodo toggleFavAction in un evento onClick e passiamo lo state , l' dispatch e gli oggetti di scena episode come argomenti alla funzione.
Infine, esaminiamo lo stato favorite per verificare se fav.id (ID preferito) corrisponde a episode.id . In tal caso, alterniamo tra il testo Unfav e Fav . Questo aiuta l'utente a sapere se ha preferito quell'episodio o meno.
Ci stiamo avvicinando alla fine. Ma abbiamo ancora bisogno di una pagina a cui collegare gli episodi preferiti quando l'utente sceglie tra gli episodi in home page.
Se sei arrivato fin qui, datti una pacca sulla spalla.
Componente Favpage
Nella cartella dei components , crea un file FavPage.tsx . Copia e incolla il codice seguente:
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> ) } Per creare la logica alla base della scelta degli episodi preferiti, abbiamo scritto un piccolo codice. Importiamo lazy e Suspense da React. Importiamo anche Store , IEpisodeProps e toggleFavAction dai rispettivi file.
Importiamo il nostro componente EpisodesList utilizzando la funzione React.lazy . Infine, restituiamo il componente App . Al suo interno, utilizziamo il wrapper Suspense e impostiamo un fallback su un div con il testo di caricamento.
Funziona in modo simile al componente Homepage . Questo componente accederà allo store per ottenere gli episodi che l'utente ha preferito. Quindi, l'elenco degli episodi viene passato al componente EpisodesList .
Aggiungiamo altri frammenti al file HomePage.tsx .
Includi toggleFavAction da ../Actions . Includere anche il metodo toggleFavAction come oggetti di scena.
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
La nostra FavPage deve essere collegata, quindi abbiamo bisogno di un collegamento nella nostra intestazione in App.tsx . Per raggiungere questo obiettivo, utilizziamo Reach Router, una libreria simile a React Router. William Le spiega le differenze tra Reach Router e React Router.
Nella tua CLI, esegui npm install @reach/router @types/reach__router . Stiamo installando sia la libreria Reach Router che i tipi reach-router Reach.
Al termine dell'installazione, importa Link da @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
Destrutturiamo il negozio da useContext . Infine, la nostra casa avrà un Link e un percorso verso / , mentre la nostra preferita avrà un percorso verso /faves .
{state.favourites.length} controlla il numero di episodi negli stati preferiti e lo visualizza.
Infine, nel nostro file index.tsx , importiamo rispettivamente i componenti FavPage e HomePage e li avvolgiamo nel Router .
Copia il codice evidenziato nel codice esistente:
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.pageComponentReactDOM.render( <StoreProvider><Router> <RouterPage pageComponent={<HomePage />} path='/' /> <RouterPage pageComponent={<FavPage />} path='/faves' /> </Router></StoreProvider>, document.getElementById('root') )
Ora, vediamo come funziona ADD_FAV implementato.

Rimuovi la funzionalità preferita
Infine, aggiungeremo la "funzione Rimuovi episodio", in modo che quando si fa clic sul pulsante, si alterna tra l'aggiunta o la rimozione di un episodio preferito. Visualizzeremo il numero di episodi aggiunti o rimossi nell'intestazione.
NEGOZIO
Per creare la funzionalità "Rimuovi episodio preferito", aggiungeremo un altro caso nel nostro negozio. Quindi, vai su Store.tsx e aggiungi il codice evidenziato:
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} }
Aggiungiamo ancora un altro case chiamato REMOVE_FAV e restituiamo un oggetto contenente la copia del nostro initialState . Inoltre, lo stato dei favorites contiene il carico utile dell'azione.
AZIONE
Copia il seguente codice evidenziato e incollalo in 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 typeexport 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) }
Importiamo l'interfaccia IState da ./types/interfaces , perché dovremo passarla come tipo agli oggetti di state nella funzione toggleFavAction .
Viene creata una variabile episodeInFav per verificare se esiste un episodio nello stato dei favorites .
Filtramo lo stato dei preferiti per verificare se un ID preferito non è uguale a un ID episodio. Pertanto, a dispatchObj viene riassegnato un tipo di REMOVE_FAV e un payload di favWithoutEpisode .
Vediamo in anteprima il risultato della nostra app.
Conclusione
In questo articolo, abbiamo visto come impostare TypeScript in un progetto React e come migrare un progetto da vanilla React a TypeScript.
Abbiamo anche creato un'app con TypeScript e React per vedere come viene utilizzato TypeScript nei progetti React. Confido che tu sia stato in grado di imparare alcune cose.
Per favore condividi il tuo feedback e le tue esperienze con TypeScript nella sezione commenti qui sotto. Mi piacerebbe vedere cosa ti viene in mente!
Il repository di supporto per questo articolo è disponibile su GitHub.
Riferimenti
- "Come migrare un'app React in TypeScript", Joe Previte
- "Perché e come utilizzare TypeScript nella tua app React?", Mahesh Haldar
