Hallo Habr, heute werden wir mit TypeScript und React-Hooks arbeiten. Dieses Tutorial hilft Ihnen, die Grundlagen des "Skripts" zu verstehen und an einer Testaufgabe für das Front-End zu arbeiten .
Testaufgaben zum Projekt "Ohne Wasser" bieten die Möglichkeit, eine Codeüberprüfung durchzuführen. Die Frist für den aktuellen Auftrag endet am 11. April 2019.

Videoversion
Wenn Sie zu faul zum Lesen sind, besuchen Sie das Webinar am 20. März um 21:00 Uhr Moskauer Zeit. Registrierung (ohne E-Mails und SMS). Webinar abgehalten, Webinar- Aufzeichnung .
Vorbereitung
Zum Starten können Sie die TypeScript- Version " Create-React-App" verwenden oder meinen Starter verwenden (der bereits den Reach-Router enthält).
Ich werde meinen Starter verwenden (mehr dazu im Abschnitt "Übung").
TypeScript-Theorie
TS löst das Problem der "dynamischen Typisierung" in JavaScript, wenn Ihre Variable unterschiedliche Werte annehmen kann. Jetzt eine Linie, dann eine Zahl oder sogar ein Objekt. Es war so bequem, im „19. Jahrhundert“ zu schreiben, aber jetzt sind sich alle einig, dass die Codebasis einfacher zu pflegen ist, wenn Sie vordefinierte Typen haben (Regeln berücksichtigen). Und es gibt weniger Fehler in der Entwicklungsphase.
Wenn Sie beispielsweise eine Komponente haben, die eine Nachricht anzeigt, können Sie den folgenden Typ für die Nachricht angeben:
Daher haben wir strenge "statische" Typen für die Eigenschaften unseres "Nachrichten" -Objekts angegeben. Wenn wir versuchen, eine nicht vorhandene Eigenschaft abzurufen, zeigt TypeScript einen Fehler an.
import * as React from 'react' import { INewsItem } from '../models/news'

Außerdem zeigen Visual Studio Code und andere erweiterte Editoren einen Fehler an:

Optisch bequem. In meinem Fall zeigt VS Code zwei Fehler gleichzeitig an: Der Typ der Variablen ist nicht festgelegt (dh er ist in unseren "Schnittstellen" -Nachrichten nicht vorhanden) und "Die Variable wird nicht verwendet". Darüber hinaus werden nicht verwendete Variablen bei Verwendung von TypeScript im VS-Code standardmäßig blass hervorgehoben.
Hier lohnt es sich, in einer Zeile den Grund für die enge Integration von TypeScript und VS Code anzugeben: Beide Produkte sind von Microsoft entwickelt worden.
Was kann uns TypeScript noch sofort sagen? Im Kontext von Variablen sprechen, das war's. TS ist sehr mächtig, er versteht was was ist.
const NewsItem: React.FC<INewsItemProps> = ({ data: { id, title, text } }) => { return ( <article> <div>{id.toUpperCase()}</div> {/* , , 'number' toUpperCase() */} <div>{title.toUpperCase()}</div> {/* ! */} <div>{text}</div> </article> ) }

Hier schwört TypeScript sofort auf eine nicht vorhandene Eigenschaft - toUpperCase
mit Typnummer. Und wie wir wissen, haben nur Zeichenfolgentypen toUpperCase () -Methoden.
Stellen Sie sich nun vor, Sie schreiben den Namen einer Funktion, öffnen die Klammer und der Editor zeigt Ihnen sofort ein Popup-Hilfefenster an, das angibt, welche Argumente und welcher Typ an die Funktion übergeben werden können.
Oder stellen Sie sich vor, Sie haben die Empfehlungen genau befolgt und die Eingabe in Ihr Projekt ist kugelsicher. Zusätzlich zur automatischen Substitution wird das Problem mit impliziten ( undefined
) Werten im Projekt behoben.
Übe
Wir schreiben die erste Testaufgabe für React-Hooks + TypeScript neu. Lassen Sie zunächst Redux weg , andernfalls kopieren Sie einfach alles von hier, anstatt an " neu gestartetem TK # 1 " zu arbeiten.
Toolkit
(für diejenigen, die VS Code verwenden )
Der Einfachheit halber empfehle ich Ihnen, die TSLint-Erweiterung zu installieren.
Fügen Sie in den Editoreinstellungen Folgendes hinzu, um die automatische Fehlerbehebung für TSLint-Fehler zum Zeitpunkt des Speicherns zu aktivieren:
Sie können die Einstellungen über das Menü aufrufen oder sehen, wo sie sich physisch in Ihrem Betriebssystem befinden.
TSLint-Einstellungen sind Standard und ich habe eine Regel deaktiviert.
{ "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"], "linterOptions": { "exclude": [ "node_modules/**/*.ts", "src/serviceWorker.js" ] }, "rules": { "object-literal-sort-keys": false
Die Integration ist vorbei!
Wir schreiben eine Bewerbung
Wir werden uns im Laufe des Stücks mit neuen Dingen vertraut machen. Klonen Sie zunächst Ihren 1-Start-Zweig oder synchronisieren Sie ihn mit meinem Code in Ihrem Code.
Alle Skriptdateien vom Reaktionstyp haben die Erweiterung .tsx .
Was ist in der Startvorlage interessant?
- Pre-Commit-Hook (wir haben dies bereits besprochen: Text , Video )
- TSLint ( ESLint- Ersatz)
- zu startende Dateien und Abhängigkeiten für diesen und die nächsten Schritte
Beginnen wir mit src / App.tsx :
import * as React from 'react' import './App.css' const App = () => { return ( <div className="container"> <h1>TZ #1 with hooks & TypeScript</h1> <nav> <p></p> </nav> <p> </p> </div> ) } const RoutedApp = () => { return <App /> } export { RoutedApp }

Ok, Standardstart. Versuchen wir, <App />
eine Eigenschaft hinzuzufügen
src / App.tsx
const App = props => { return ( <div className="container"> <h1>TZ #1 with hooks & TypeScript</h1> <nav> <p></p> </nav> <p> </p> {/* name props */} <p>, {props.name}</p> </div> ) } // name const RoutedApp = () => { return <App name="Max Frontend" /> }
Wir bekommen den Fehler:

(Wenn Sie den Fehler nicht erhalten haben, überprüfen Sie die Strenge Ihrer tsconfig.json-Einstellungen. Es sollte eine noImplicitAny-Regel geben.)
Sie vermuten bereits aus der Übersetzung des Fehlertextes, dass unsere Eigenschaften vom Typ any sein sollten . Dieser Typ kann als "alles" übersetzt werden. Unser Projekt hat eine Regel, die implizite Ausgaben dieses Typs verbietet.
- Implizite Typinferenz?
- Genau! TypeScript kann standardmäßig den Typ einer Variablen ableiten und kommt damit gut zurecht. Dies wird als Typinferenz bezeichnet.
Ein Beispiel:
let x = 3
Im Fall von props
- TS kann den Typ der Variablen nicht zu 100% bestimmen und sagt daher - lassen Sie es
(dh geben Sie any
). Dies erfolgt implizit und ist durch die Regel noImplicitAny in den Projekteinstellungen (tsconfig.json) verboten.
Wir können den Typ any explizit angeben und der Fehler wird verschwinden. Der Typ der Variablen wird durch einen Doppelpunkt angezeigt.
Fertig, es gibt keine Fehler, das Projekt funktioniert, aber was nützt eine solche Eingabe, wenn props
alles sein können? Wir wissen mit Sicherheit, dass unser Name eine
. Daraus folgt die Regel:
Vermeiden Sie Typ
Es gibt Zeiten, in denen any
notwendig ist und dies normal ist, aber es ist sofort ein Schlag unter die Taille durch striktes Tippen.
Um props
zu beschreiben, verwenden wir das Schlüsselwort interface
:
Wenn Sie versuchen, den Namenstyp in number
ändern, tritt sofort ein Fehler auf.

Darüber hinaus wird der Fehler auch in VS Code (und vielen anderen Editoren) hervorgehoben. Der Fehler zeigt an, dass wir keine Übereinstimmung haben: Wir übergeben eine Zeichenfolge, aber wir erwarten eine Zahl.
Wir werden props
und <App />
eine weitere props
Site hinzufügen
src / App.tsx
interface IAppProps { name: string; } const App = (props: IAppProps) => { return ( <div className="container"> <h1>TZ #1 with hooks & TypeScript</h1> <nav> <p></p> </nav> <p> </p> <p>, {props.name}</p> {/* site */} <p>: {props.site}</p> </div> ) } // site const RoutedApp = () => { return <App name="Max Frontend" site="maxpfrontend.ru" /> }
Ich habe einen Fehler erhalten:
Type error: Property 'site' does not exist on type 'IAppProps'. TS2339
Die site
Eigenschaft ist im Typ IAppProps
nicht vorhanden. Hier möchte ich sofort sagen, dass wir unter dem Typnamen sofort verstehen, wo wir suchen müssen. Benennen Sie daher die Typen korrekt.
Bevor wir props.site
beheben, gehen wir wie props.site
: Löschen Sie den Absatz, in dem props.site
.
Wir erhalten einen weiteren Fehlertext:

Hier möchte ich nur erwähnen, was TS abgeleitet hat: site
ist eine Art string
(dies ist im Screenshot unterstrichen).
Fix:
interface IAppProps { name: string; site: string;
Keine Fehler, keine Probleme.
Um mit Routing arbeiten zu können, müssen Kinder rendern. Lassen Sie uns vor uns gehen und versuchen, eine "untergeordnete Komponente" zu zeichnen.
const App = (props: IAppProps) => { return ( <div className="container"> <h1>TZ #1 with hooks & TypeScript</h1> ... // <p>: {props.site}</p> {props.children} </div> ) } const Baby = () => { return <p> </p> } const RoutedApp = () => { return ( <App name="Max Frontend" site="maxpfrontend.ru"> <Baby /> </App> ) }
TS schwört, sie sagen es und so - children
in IAppProps
nicht beschrieben.

Natürlich möchten wir einige Standard-Dinge nicht "typisieren", und hier kommt die Community zur Rettung, die bereits viel vor uns getippt hat. Zum Beispiel enthält das Paket @ types / react alle Eingaben für react.
Durch die Installation dieses Pakets (in meinem Beispiel ist es bereits installiert) können wir den folgenden Eintrag verwenden:
React.FunctionComponent<P> React.FC<P>
Dabei sind <P>
die Typen für unsere props
, props
der Datensatz hat die Form.
React.FC<IAppProps>
Für diejenigen, die gerne große Textmengen lesen, kann ich vor dem Üben einen Artikel über " Generika " anbieten (das gleiche <und>). Im Übrigen reicht es vorerst aus, diesen Satz wie folgt zu übersetzen: eine funktionale Komponente, die <so und so Eigenschaften> akzeptiert.
Der Eintrag für die App-Komponente ändert sich etwas. Vollversion.
src / App.tsx
// , // React React.XXX, // XXX - import * as React from 'react' // , , // @types/react // interface IAppProps { name: string; site: string; } // const App: React.FC<IAppProps> = props => { return ( <div className="container"> <h1>TZ #1 with hooks & TypeScript</h1> <nav> <p></p> </nav> <p> </p> <p>, {props.name}</p> <p>: {props.site}</p> {props.children} </div> ) } const Baby = () => { return <p> </p> } const RoutedApp = () => { return ( <App name="Max Frontend" site="maxpfrontend.ru"> <Baby /> </App> ) }
Analysieren wir die folgende Zeile in Zeichen:
const App: React.FC<IAppProps> = props => {
- Warum ist der Typ nach props
verschwunden?
- Weil nach der App
- hinzugefügt.
Wir haben aufgezeichnet, dass die App-Variable vom Typ React.FC<IAppProps>
.
React.FC
ist eine Art von "Funktion", und in <> haben wir angegeben, welche Art von Argument es braucht, IAppProps
wir haben angegeben, dass unsere props
vom Typ IAppProps
.
(Es besteht die Gefahr, dass ich Sie ein wenig belogen habe, aber um das Beispiel zu vereinfachen, denke ich, dass es in Ordnung ist)
Insgesamt: Wir haben gelernt, die Art der Eigenschaften für die übertragenen props
anzugeben, ohne dabei "deren" Eigenschaften von React-Komponenten zu verlieren.
Der Quellcode im Moment.
Routing hinzufügen
Wir werden den Reach-Router verwenden , um unseren Horizont zu erweitern. Dieses Paket ist dem React-Router sehr ähnlich.
Fügen Sie die Seite - News (News) hinzu, bereinigen Sie die <App />
.
src / pages / News.tsx
import * as React from 'react' const News = () => { return ( <div className="news"> <p></p> </div> ) } export { News }
src / App.tsx
import * as React from 'react'
Die Anwendung ist fehlerhaft, ein Fehler (einer der Fehler, da das Terminal den ersten anzeigt):

Wir sind an diesen Datensatz bereits ein wenig gewöhnt und verstehen daraus, dass der path
in der Typbeschreibung für <App />
nicht vorhanden ist.
Wieder wird alles vor uns beschrieben. Wir werden das Paket @ types / aim__router und den Typ RouteComponentProps
verwenden. Um unsere Eigenschaften nicht zu verlieren, verwenden wir das extends
.
import * as React from 'react'
Für Neugierige, welche Typen in RouteComponentProps beschrieben sind .
Der Fehler in <App />
verschwunden, blieb aber in <News />
, da wir für diese Komponente keine Eingabe angegeben haben.
Mini-Puzzle: Geben Sie die Eingabe für <News />
. Derzeit werden dort nur Eigenschaften vom Router übertragen.
Die Antwort lautet:
src / Pages / News.tsx
import * as React from 'react' import { RouteComponentProps } from '@reach/router'

Wir gehen weiter und fügen eine Route mit einem Parameter hinzu. Die Parameter im Reach-Router leben direkt in Requisiten. Wie Sie sich erinnern, leben sie im props.match
-Router in props.match
.
src / App.tsx
import * as React from 'react' import { Link, RouteComponentProps, Router } from '@reach/router' import { About } from './pages/About' import { News } from './pages/News'
src / pages / About.tsx
import * as React from 'react' import { RouteComponentProps } from '@reach/router' const About: React.FC<RouteComponentProps> = props => { return ( <div className="about"> <p> about</p> {/* source */} <p>{props.source}</p> </div> ) } export { About }
Ein Fehler, den wir nicht erwartet hatten:

Die Quelleigenschaft existiert nicht ... Einerseits Ratlosigkeit: Wir übergeben sie an den Pfad, der Zeichenfolge ist, andererseits Freude: Ah, nun, wie haben die Autoren der Bibliothek und der Eingabe versucht, uns diese Warnung hinzuzufügen.
Um dies zu beheben, verwenden wir eine der folgenden Optionen : Erweitern Sie RouteComponentProps
und geben Sie die optionale RouteComponentProps
. Optional, da es möglicherweise nicht in unserer URL enthalten ist.
TypeScript verwendet ein Fragezeichen, um eine optionale Eigenschaft anzugeben.
src / pages / About.tsx
import * as React from 'react' import { RouteComponentProps } from '@reach/router' interface IAboutProps extends RouteComponentProps { source?: string;
src / App.tsx (gleichzeitig werden wir die Navigation russifizieren)
import * as React from 'react' import { Link, RouteComponentProps, Router } from '@reach/router' import { About } from './pages/About' import { News } from './pages/News' import './App.css' interface IAppProps extends RouteComponentProps { name: string; site: string; } const App: React.FC<IAppProps> = props => { return ( <div className="container"> <h1>TZ #1 with hooks & TypeScript</h1> <nav> <Link to="/"></Link> <Link to="news"></Link>{' '} <Link to="/about/habr"> habr</Link>{' '} </nav> <hr /> <p> {' '} : {props.name} | : {props.site} </p> <hr /> {props.children} </div> ) } const RoutedApp = () => { return ( <Router> <App path="/" name="Max Frontend" site="maxpfrontend.ru"> <News path="/news" /> <About path="/about/:source" /> </App> </Router> ) } export { RoutedApp }

Insgesamt : Wir haben gelernt, die an der Rotation beteiligten Komponenten zu typisieren.
Der Quellcode im Moment.
Lassen Sie uns mit Hooks arbeiten und weiter tippen
Ich erinnere Sie daran, dass unsere Aufgabe darin besteht, eine Testaufgabe zu implementieren, jedoch ohne Redux.
Ich habe einen Zweig vorbereitet, um diesen Schritt mit Routing, einem nicht funktionierenden Anmeldeformular und den erforderlichen Seiten zu beginnen.

Nachrichten herunterladen
Nachrichten sind eine Reihe von Objekten.
Präsentieren Sie unsere Neuigkeiten:
{ id: 1, title: ' CRUD React-hooks', text: ' CRUD- ', link: 'https://maxpfrontend.ru/perevody/delaem-crud-prilozhenie-s-pomoschyu-react-hooks/', timestamp: new Date('01-15-2019'), },
Schreiben wir gleich ein Modell (Typen für Nachrichten):
src / models / news.ts (Erweiterung .ts )
export interface INewsItem { id: number; title: string; text: string; link: string; timestamp: Date; }
Aus dem neuen Zeitstempel wird der Typ Date
angezeigt.
Stellen Sie sich unseren Datenanruf vor:
const fakeData = [ { id: 1, title: ' CRUD React-hooks', text: ' CRUD- ', link: 'https://maxpfrontend.ru/perevody/delaem-crud-prilozhenie-s-pomoschyu-react-hooks/', timestamp: new Date('01-15-2019'), }, { id: 2, title: ' React hooks', text: ' useState useEffect ', link: 'https://maxpfrontend.ru/perevody/znakomstvo-s-react-hooks/', timestamp: new Date('01-06-2019'), }, { id: 3, title: ' Google Sign In', text: ' Google Sign In ', link: 'https://maxpfrontend.ru/vebinary/avtorizatsiya-s-pomoschyu-google-sign-in/', timestamp: new Date('11-02-2018'), }, ] export const getNews = () => { const promise = new Promise(resolve => { resolve({ status: 200, data: fakeData,
Unser Anruf von api getNews
gibt Promise zurück , und dieses „Promise“ hat einen bestimmten Typ, den wir auch beschreiben können:
interface INewsResponse { status: number;
Ist es heiß Jetzt wird es noch heißer, da der Promise-Typ ein Generikum ist, müssen wir uns erneut mit <
und >
befassen. Dies ist der schwierigste Teil des Tutorials, daher lesen wir den endgültigen Code:
src / api / News.ts
import { INewsItem } from '../models/news'
Rauchpause.
Nachrichten anzeigen
src / pages / News.tsx
import * as React from 'react' import { RouteComponentProps } from '@reach/router' import { getNews } from '../api/news' import { NewsItem } from '../components/NewsItem'
Der Code enthält Kommentare zu TypeScript. Wenn Sie Hilfe bei React-Hooks benötigen, können Sie hier lesen: Dokumentation (EN), Tutorial (RU).
Aufgabe: Schreiben Sie eine <NewsItem />
-Komponente, die Nachrichten anzeigt. Denken Sie daran, den richtigen Typ anzugeben. Verwenden Sie das INewsItem
Modell.
Das Ergebnis könnte folgendermaßen aussehen:

Die Lösung ist unten.
src / components / NewsItem.tsx
import * as React from 'react' import { INewsItem } from '../models/news' interface INewsItemProps { data: INewsItem;
Die Frage ist, warum wir die Schnittstelle so beschrieben haben (Kommentare in Code [1] und [2]). Könnten wir einfach schreiben:
React.FC<INewsItem>
Die Antwort ist unten.
.
.
.
Könnten, da wir die Nachrichten in der data
, das heißt, wir müssen schreiben:
React.FC<{ data: INewsItem }>
Dies haben wir nur mit dem Unterschied getan, dass wir die interface
angegeben interface
, falls der Komponente andere Eigenschaften hinzugefügt wurden. Und die Lesbarkeit ist besser.
Insgesamt: Wir haben das Tippen für Promise und useEffect geübt. Beschrieb den Typ für eine andere Komponente.
Wenn Sie immer noch nicht in die Hände klatschen, weil TS automatisch ersetzt und streng ist, üben Sie entweder nicht oder striktes Tippen ist nichts für Sie. Im zweiten Fall - ich kann bei nichts helfen, das ist Geschmackssache. Ich mache Sie darauf aufmerksam, dass der Markt seine Bedingungen bestimmt und immer mehr Projekte vom Tippen leben, wenn nicht von TypeScript, dann vom Flow .
Der Quellcode im Moment.
Auth api
Schreiben Sie eine api
für die Anmeldung. Das Prinzip ist ähnlich - wir geben ein promise
mit der Antwort autorisiert / Fehler zurück. Wir werden Informationen über den Autorisierungsstatus in localStorage
( viele halten dies für eine offensichtliche Sicherheitsverletzung, ich bin ein wenig hinter Streitigkeiten zurück und weiß nicht, wie sie endete ).
In unserer Anwendung besteht die Anmeldung aus einer Reihe von Benutzernamen und Kennwörtern (beide sind Zeichenfolgen). Wir werden das Modell beschreiben:
src / models / user.ts
export interface IUserIdentity { username: string; password: string; }
src / api / auth.ts
import { navigate } from '@reach/router' import { IUserIdentity } from '../models/user'
, .
:
: . useState . event
onChange
/ onSubmit
— any
. .
React.useState
— , ( React.useState<T>
)
, , , . , /profile
( navigate
)
.
.
.
.
.
src/pages/Login.tsx
import * as React from 'react' import { navigate, RouteComponentProps } from '@reach/router' import { authenticate } from '../api/auth' import { IUserIdentity } from '../models/user'
, TS — . , , JavaScript.
: useState event
→
TypeScript' , .
, reach-router , react-router. , , , .
src/components/common/Authenticated.tsx
import * as React from 'react' import { Redirect, RouteComponentProps } from '@reach/router' import { checkAuthStatus } from '../../api/auth'
src/App.tsx
import * as React from 'react' import { Link, RouteComponentProps, Router } from '@reach/router' import { Authenticated } from './components/ommon/Authenticated' import { Home } from './pages/Home' import { Login } from './pages/Login' import { News } from './pages/News' import { Profile } from './pages/Profile' import { checkAuthStatus, logout } from './api/auth' import './App.css' const App: React.FC<RouteComponentProps> = props => { return ( <div className="container"> <h1>TZ #1 with hooks & TypeScript</h1> <nav> <Link to="/"></Link> <Link to="news"></Link>{' '} <Link to="profile"></Link>{' '} {checkAuthStatus() ? <button onClick={logout}></button> : null} </nav> {props.children} </div> ) } const RoutedApp = () => { return ( <Router> <App path="/"> <Home path="/" /> <Login path="/login" /> <News path="/news" /> <Authenticated path="/profile"> <Profile path="/" /> </Authenticated> </App> </Router> ) } export { RoutedApp }
.
. type="password"
.
, . "-", , , react-intl , react-i18next .
, . :
src/localization/formErrors.ts
const formErrors = { ru: { incorrect_login_or_password: ' ', }, en: { incorrect_login_or_password: 'Incorrect login or password', }, } export { formErrors }
<Login />
src/pages/Login.tsx
import * as React from 'react' import { navigate, RouteComponentProps } from '@reach/router' import { authenticate } from '../api/auth'
, .

TypeScript , , . , index signature ( , StackOverflow ).
interface IFormErrors { [key: string]: { [key: string]: string, }; } const formErrors: IFormErrors = { ru: { incorrect_login_or_password: ' ', }, en: { incorrect_login_or_password: 'Incorrect login or password', }, } export { formErrors }
, . , "".

→
Fazit
TypeScript. , TS . , , "one" "two" ( — union).
, — .
" " telegram youtube ( 11 2019).
Vielen Dank für Ihre Aufmerksamkeit! , :

CRA + TypeScript
TypeScript Playground
— Understanding TypeScript's type notation ( Dr.Axel Rauschmayer)
Microsoft,
TSLint
tslint
tslint
tsconfig.json tslint.json
d.ts .ts
, staging .
react-typescript-samples LemonCode
:
es5 (!)
React v16
typescript-.
Microsoft. UI- Fabric . , github .