Réécriture du scénario de test pour le frontend junior sur TypeScript et React-hooks

Salut Habr, aujourd'hui, nous allons travailler avec TypeScript et React-hooks. Ce didacticiel vous aidera à comprendre les bases du "script" et vous aidera à travailler sur une tâche de test pour le front-end .


Les tâches de test sur le projet "Sans eau" sont l'occasion d'obtenir une révision de code. La date limite pour la mission en cours est le 11 avril 2019.



Version vidéo


Si vous êtes trop paresseux pour lire, venez au webinaire le 20 mars à 21h00, heure de Moscou. Inscription (sans e-mails ni sms). Webinaire organisé, enregistrement du webinaire.


La préparation


Pour commencer, vous pouvez prendre la version TypeScript de Create-react-app , ou utiliser mon démarreur (qui inclut déjà reach-router)


Je vais utiliser mon entrée (plus à ce sujet dans la section "Pratique").


Théorie de TypeScript


TS résout le problème du "typage dynamique" en JavaScript, lorsque votre variable peut prendre différentes valeurs. Maintenant une ligne, puis un nombre, ou même un objet. Il était si pratique d'écrire au «19e siècle», cependant, maintenant tout le monde convient que si vous avez des types prédéfinis (pensez aux règles), la base de code est plus facile à maintenir. Et il y a moins de bogues au stade du développement.


Par exemple, si vous avez un composant qui affiche une nouvelle, nous pouvons spécifier le type suivant pour la nouvelle:


//   -   { } // c  export interface INewsItem { id: number; // id  -   title: string; // title () -  text: string; // text ( ) -  link: string; // link () -  timestamp: Date; // timestamp () -    js } 

Ainsi, nous avons spécifié des types "statiques" stricts pour les propriétés de notre objet "news". Si nous essayons d'obtenir une propriété inexistante, TypeScript affichera une erreur.


 import * as React from 'react' import { INewsItem } from '../models/news' //  "" interface INewsItemProps { data: INewsItem; //  ,   (    ) } const NewsItem: React.FC<INewsItemProps> = ({ data: { id, text, abracadabra }, //    , id  text - , abracadabra -  }) => { return ( <article> <div>{id}</div> <div>{text}</div> </article> ) } export { NewsItem } 


De plus, Visual Studio Code et d'autres éditeurs avancés vous montreront une erreur:



Visuellement, pratique. Dans mon cas, VS Code affiche deux erreurs à la fois: le type de la variable n'est pas défini (c'est-à-dire qu'il n'existe pas dans notre actualité "interface") et "la variable n'est pas utilisée". De plus, les variables inutilisées lors de l'utilisation de TypeScript sont surlignées en couleur pâle dans VS Code par défaut.


Ici, il vaut la peine d'indiquer sur une seule ligne la raison d'une telle intégration étroite de TypeScript et VS Code: les deux produits sont le développement de Microsoft.


Que peut nous dire d'autre TypeScript tout de suite? Parlant dans le contexte des variables, c'est tout. TS est très puissant, il comprend quoi.


 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> ) } 


Ici, TypeScript jure immédiatement sur une propriété inexistante - toUpperCase de number de type. Et comme nous le savons, en effet, seuls les types de chaîne ont des méthodes toUpperCase () .


Imaginez maintenant, vous commencez à écrire le nom d'une fonction, ouvrez le crochet et l'éditeur vous montre immédiatement une fenêtre d'aide contextuelle, qui indique quels arguments et quel type peuvent être passés à la fonction.


Ou imaginez - vous avez strictement suivi les recommandations et taper sur votre projet est à l'épreuve des balles. En plus de la substitution automatique, vous vous débarrassez du problème des valeurs implicites ( undefined ) sur le projet.


Pratique


Nous réécrivons la première tâche de test sur react-hooks + TypeScript. Pour l'instant, omettons Redux , sinon au lieu de travailler sur " TK # 1 redémarré ", il vous suffit de tout copier à partir d'ici.


Boîte à outils


(pour ceux qui utilisent VS Code )


Pour plus de commodité, je vous recommande d'installer l'extension TSLint .


Pour activer la correction automatique des erreurs TSLint au moment de l'enregistrement, ajoutez dans les paramètres de l'éditeur:


 //  settings.json visual studio "editor.codeActionsOnSave": { "source.fixAll.tslint": true } 

Vous pouvez accéder aux paramètres via le menu ou voir où ils vivent physiquement dans votre système d'exploitation.


Les paramètres TSLint sont standard, plus j'ai désactivé une règle.


 { "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"], "linterOptions": { "exclude": [ "node_modules/**/*.ts", "src/serviceWorker.js" ] }, "rules": { "object-literal-sort-keys": false //        } } 

L'intégration est terminée!


Nous rédigeons une candidature


Nous nous familiariserons avec de nouvelles choses au cours de la pièce. Pour commencer, clonez votre branche 1-start ou synchronisez avec mon code dans votre code.


Tous les fichiers de script de type React dont nous disposons portent l'extension .tsx .


Qu'est-ce qui est intéressant dans le modèle de départ?


  • crochet de pré-validation (nous en avons déjà discuté: texte , vidéo )
  • TSLint (remplacement ESLint )
  • fichiers à démarrer et dépendances pour cela et les étapes suivantes

Commençons avec 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, départ standard. Essayons d'ajouter une propriété à <App />


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" /> } 

Nous obtenons l'erreur:



(si vous n'avez pas obtenu l'erreur, vérifiez la rigueur de vos paramètres tsconfig.json, il devrait y avoir une règle noImplicitAny)


Vous devinez déjà d'après la traduction du texte d'erreur que nos propriétés ne doivent pas être de type quelconque . Ce type peut être traduit par "n'importe quoi". Notre projet a une règle qui interdit la sortie implicite de ce type.


- Inférence de type implicite?


- Exactement! TypeScript est capable de déduire le type d'une variable par défaut et il s'en sort bien. C'est ce qu'on appelle l' inférence de type.


Un exemple:


 let x = 3 // TS ,  x   number,   number  " " (implicit) let x: number = 3 //   (explicit) ,  x   number //       , //       TS      

Dans le cas des props - TS ne peut pas déterminer le type de la variable à 100% et dit donc - que ce soit (c'est-à-dire, tapez any ). Cela se fait implicitement et est interdit par la règle noImplicitAny dans les paramètres du projet (tsconfig.json)


Nous pouvons explicitement spécifier le type any et l'erreur disparaîtra. Le type de la variable est indiqué par deux points.


 //    :any //    "props: any"        //    const App = (props: any) => { return ( <div className="container"> <h1>TZ #1 with hooks & TypeScript</h1> <nav> <p></p> </nav> <p> </p> <p>, {props.name}</p> </div> ) } 

Terminé, il n'y a pas d'erreurs, le projet fonctionne, mais à quoi ça sert quand les props peuvent être n'importe quoi? Nous savons avec certitude que notre nom est une . La règle en découle:


Essayez d'éviter tout type

Il y a des moments où any nécessaire et c'est normal, mais c'est immédiatement un coup en dessous de la taille par une frappe stricte.


Pour décrire les types d' props , nous utiliserons le mot-clé interface :


 //  ,   IAppProps //    I,      TSLint  //  I     interface IAppProps { name: string; //  name   string } //        props 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> </div> ) } 

Essayez de changer le type de name en number et une erreur se produira immédiatement.



De plus, l'erreur sera également accentuée dans VS Code (et de nombreux autres éditeurs). L'erreur indique que nous n'avons pas de correspondance: nous passons une chaîne, mais nous attendons un nombre.


Nous props et ajouter un autre props - site à <App />


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" /> } 

Vous avez une erreur:


 Type error: Property 'site' does not exist on type 'IAppProps'. TS2339 

La propriété de site n'existe pas dans le type IAppProps . Ici, je veux immédiatement dire que par le nom du type, nous comprenons immédiatement où chercher. Par conséquent, nommez les types correctement.


Avant de le corriger, faisons ceci: supprimez le paragraphe qui props.site .


Nous obtenons un autre texte d'erreur:



Ici, je veux noter uniquement ce que TS a déduit: site est un type de string (cela est souligné dans la capture d'écran).


Correction:


 interface IAppProps { name: string; site: 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> <p>: {props.site}</p> </div> ) } const RoutedApp = () => { return <App name="Max Frontend" site="maxpfrontend.ru" /> } 

Pas d'erreurs, pas de problèmes.


Pour travailler avec le routage, nous avons besoin du rendu des enfants. Prenons de l'avance sur nous-mêmes et essayons de dessiner un "composant enfant".


 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 jure, ils disent ceci et cela - les children ne children pas décrits dans IAppProps .



Bien sûr, nous ne voudrions pas "typifier" certaines choses standard, et ici la communauté vient à la rescousse, qui a déjà beaucoup tapé avant nous. Par exemple, le package @ types / react contient toutes les saisies pour react.


En installant ce package (dans mon exemple, il est déjà installé), nous pouvons utiliser l'entrée suivante:


 React.FunctionComponent<P>    React.FC<P> 

<P> sont les types de nos props , c'est-à-dire que l'enregistrement prendra la forme.


 React.FC<IAppProps> 

Pour ceux qui aiment lire de gros volumes de texte, avant de pratiquer, je peux proposer un article sur les " génériques " (ceux <et>). Pour le reste, pour le moment, il suffit de traduire cette phrase comme ceci: un composant fonctionnel qui accepte <telle ou telle propriété>.


L'entrée pour le composant App changera un peu. Version complète.


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> ) } 

Analysons la ligne suivante en caractères:


 const App: React.FC<IAppProps> = props => { 

- Pourquoi le type a-t-il disparu après les props ?


- Parce que, après l' App - ajouté.


Nous avons enregistré que la variable App sera du type: React.FC<IAppProps> .


React.FC est un type de "fonction", et à l'intérieur de <> nous avons indiqué le type d'argument qu'il prend, c'est-à-dire que nous avons indiqué que nos props seront de type IAppProps .


(il y a un risque que je vous mente un peu en termes, mais pour simplifier l'exemple, je pense que c'est ok)


Total: nous avons appris à spécifier le type de propriétés des props transmis, sans perdre "leurs" propriétés des composants React.


Le code source pour le moment.


Ajouter un routage


Nous utiliserons reach-router pour élargir nos horizons. Ce package est très similaire à react-router.


Ajoutez la page - News (News), nettoyez <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' //    reach-router import { Link, Router } from '@reach/router' import { News } from './pages/News' import './App.css' interface IAppProps { name: string; site: string; } const App: React.FC<IAppProps> = props => { return ( <div className="container"> <h1>TZ #1 with hooks & TypeScript</h1> <nav> <Link to="/">Home</Link> <Link to="news">News</Link>{' '} </nav> <hr /> <p> {' '} : {props.name} | : {props.site} </p> <hr /> {props.children} </div> ) } //  Baby,  News.  app -  path const RoutedApp = () => { return ( <Router> <App path="/" name="Max Frontend" site="maxpfrontend.ru"> <News path="/news" /> </App> </Router> ) } export { RoutedApp } 

L'application est cassée, une erreur (une des erreurs, puisque le terminal affiche la première):



Nous sommes déjà un peu habitués à cet enregistrement, et nous en comprenons que le path n'existe pas dans la description de type pour <App /> .


Encore une fois, tout est décrit devant nous. Nous utiliserons le package @ types / reach__router et le type RouteComponentProps . Afin de ne pas perdre nos propriétés, nous utiliserons le extends .


 import * as React from 'react' //   RouteComponentProps  - // ts  ,     import { Link, RouteComponentProps, Router } from '@reach/router' import { News } from './pages/News' import './App.css' // extends       RouteComponentProps //     interface IAppProps extends RouteComponentProps { name: string; site: string; } // ...    

Pour les curieux, quels types sont décrits dans RouteComponentProps .


L'erreur dans <App /> disparu, mais est restée dans <News /> , car nous n'avons pas spécifié de saisie pour ce composant.


Mini puzzle: spécifiez la saisie pour <News /> . Pour le moment, seules les propriétés du routeur y sont transférées.


La réponse est:


src / Pages / News.tsx


 import * as React from 'react' import { RouteComponentProps } from '@reach/router' //      RouteComponentProps //       P ( React.FC<P> ) const News: React.FC<RouteComponentProps> = () => { return ( <div className="news"> <p></p> </div> ) } export { News } 


Nous continuons et ajoutons une route avec un paramètre. Les paramètres de reach-router vivent directement dans les accessoires. Dans react-router, comme vous vous en souvenez, ils vivent dans des 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' // ... () const RoutedApp = () => { return ( <Router> <App path="/" name="Max Frontend" site="maxpfrontend.ru"> <News path="/news" /> {/*     source */} <About path="/about/:source" /> </App> </Router> ) } export { RoutedApp } 

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 } 

Une erreur à laquelle nous ne nous attendions pas:



La propriété source n'existe pas ... D'une part, perplexité: nous la passons à path, qui est une chaîne, d'autre part, joy: Ah, eh bien, comment les auteurs de la bibliothèque et la frappe ont-ils essayé de nous ajouter cet avertissement.


Pour le corriger, nous utiliserons l' une des options : étendre à partir de RouteComponentProps et spécifier la propriété source facultative. Facultatif, car il peut ne pas figurer dans notre URL.


TypeScript utilise un point d'interrogation pour indiquer une propriété facultative.


src / pages / About.tsx


 import * as React from 'react' import { RouteComponentProps } from '@reach/router' interface IAboutProps extends RouteComponentProps { source?: string; //  source - ,      (    props.source  undefined) } const About: React.FC<IAboutProps> = props => { return ( <div className="about"> <p> about</p> <p>{props.source}</p> </div> ) } export { About } 

src / App.tsx (en même temps, nous russifierons la navigation)


 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 } 


Total : nous avons appris à caractériser les composants impliqués dans la rotation.


Le code source pour le moment.




Travaillons avec des crochets et continuons à taper


Je vous rappelle que notre tâche consiste à implémenter une tâche de test , mais sans Redux.


J'ai préparé une branche pour commencer cette étape avec le routage, un formulaire de connexion non fonctionnel et les pages nécessaires.



Téléchargez les nouvelles


Les actualités sont un ensemble d'objets.


Présentez nos actualités:


 { 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'), }, 

Écrivons tout de suite un modèle (types d'actualités):


src / models / news.ts (extension .ts )


 export interface INewsItem { id: number; title: string; text: string; link: string; timestamp: Date; } 

À partir du nouvel horodatage unique a indiqué le type Date .


Imaginez notre appel de données:


 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, //    }) }) return promise //  promise } 

Notre appel depuis api getNews renvoie Promise , et cette «promesse» a un certain type, que nous pouvons également décrire:


 interface INewsResponse { status: number; //  -  data: INewsItem[]; // data -  ,    INewsItem [1] errorText?: string; // ,   errorText  ,     } // [1] ,   models      export interface INewsItem { id: number; title: string; text: string; link: string; timestamp: Date; } //   __[] -      __ // [{__}, {__}, {__}] 

Est-il chaud Maintenant, il sera encore plus chaud, puisque le type Promise est un générique, nous devrons à nouveau faire face à < et > . C'est la partie la plus difficile du tutoriel, nous lisons donc le code final:


src / api / News.ts


 import { INewsItem } from '../models/news' //    interface INewsResponse { //    __ status: number; data: INewsItem[]; errorText?: string; } const fakeData = [ //...  ] //    //    : // const myFunc = ():__ { return _ } // getNews -  ,    () ( ) //    Promise //   Promise -  generic,   : //  Promise<T>,  T -  ,     [1] //   ,  T ,   - INewsResponse export const getNews = (): Promise<INewsResponse> => { //  ,  [1] const promise = new Promise<INewsResponse>(resolve => { // [2] resolve({ status: 200, data: fakeData, }) }) return promise //    promise [2]  Promise<INewsResponse> } 

Pause fumée.


Afficher les actualités


src / pages / News.tsx


 import * as React from 'react' import { RouteComponentProps } from '@reach/router' import { getNews } from '../api/news' import { NewsItem } from '../components/NewsItem' //    import { INewsItem } from '../models/news' const News: React.FC<RouteComponentProps> = () => { // useState -   ,     T //   ,  T -    INewsItem //  ,    ,     [] const [news, setNews] = React.useState<INewsItem[]>([]) // <-       React.useEffect(() => { getNews() .then(res => { setNews(res.data) }) .catch(err => { //   TSLint     console.log // , ""     // tslint:disable-next-line: no-console console.warn('Getting news problem', err) }) }, []) return ( <div className="news"> {news.map(item => ( <NewsItem data={item} key={item.id} /> ))} </div> ) } export { News } 

Le code donne des commentaires liés à TypeScript. Si vous avez besoin d'aide avec react-hooks, vous pouvez lire ici: documentation (EN), tutorial (RU).


Tâche: écrire un <NewsItem /> qui affichera les actualités. N'oubliez pas de spécifier le type correct. Utilisez le modèle INewsItem .


Le résultat peut ressembler à ceci:



La solution est ci-dessous.


src / components / NewsItem.tsx


 import * as React from 'react' import { INewsItem } from '../models/news' interface INewsItemProps { data: INewsItem; // [1] } // [2] const NewsItem: React.FC<INewsItemProps> = ({ data: { title, text, timestamp, link }, }) => { return ( <article> <br /> <div> { <a href={link} target="_blank"> {title} </a> }{' '} | {timestamp.toLocaleDateString()} </div> <div>{text}</div> </article> ) } export { NewsItem } 

La question est de savoir pourquoi nous avons décrit l'interface de cette manière (commentaires dans les codes [1] et [2]). Pourrions-nous simplement écrire:


 React.FC<INewsItem> 

La réponse est ci-dessous.


.


.


.


Pourrait, puisque nous passons les nouvelles dans la propriété data , c'est-à-dire, nous devons écrire:


 React.FC<{ data: INewsItem }> 

Ce que nous avons fait, à la différence près que nous avons spécifié l' interface , au cas où d'autres propriétés seraient ajoutées au composant. Et la lisibilité est meilleure.


Total: Nous avons pratiqué la dactylographie pour Promise et useEffect. Décrit le type d'un autre composant.


Si vous ne battez toujours pas les mains contre la substitution automatique et la rigueur de TS, alors vous ne pratiquez pas ou la frappe stricte n'est pas pour vous. Dans le deuxième cas - je ne peux rien faire d'autre, c'est une question de goût. J'attire votre attention sur le fait que le marché dicte ses conditions et de plus en plus de projets vivent avec la dactylographie, sinon avec TypeScript, puis avec le flux .


Le code source pour le moment.




API d'authentification


Écrivez l' api pour la connexion. Le principe est similaire - nous renvoyons une promise avec la réponse autorisée / erreur. Nous stockerons des informations sur le statut d'autorisation dans localStorage ( beaucoup considèrent cela comme une violation flagrante de la sécurité, je suis un peu en retard sur les litiges et je ne sais pas comment cela s'est terminé ).


Dans notre application, la connexion est un tas de nom d'utilisateur et de mot de passe (les deux sont des chaînes), nous décrirons le modèle:


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' //        //    data -  any ( ,  ) // ,    ,      , //    ,      ( ) interface IAuthResponse { status: number; data?: any; [1] errorText?: string; } // -,  -      //      IUserIdentity //    -  boolean  (true  false) const checkCredentials = (data: IUserIdentity): boolean => { if (data.username === 'Admin' && data.password === '12345') { return true } else { return false } } //   "-",    ,       //    ,      1  - data //    Promise<T>,  T -  IAuthResponse export const authenticate = (data: IUserIdentity): Promise<IAuthResponse> => { const promise = new Promise<IAuthResponse>((resolve, reject) => { if (!checkCredentials(data)) { reject({ status: 500, errorText: 'incorrect_login_or_password', }) } window.localStorage.setItem('tstz.authenticated', 'true') resolve({ status: 200, data: 'ok', //    -  string,     IAuthResponse [1] any  string }) }) return promise } //   ,    //  0  () //  true  false ( boolean) export const checkAuthStatus = (): boolean => { if (localStorage.getItem('tstz.authenticated')) { return true } else { return false } } //   ,  0  //    (    void) export const logout = (): void => { window.localStorage.removeItem('tstz.authenticated') navigate('/') //      url- (reach-router) } 

, .


:




: . useState . event onChange / onSubmitany . .


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' //    ,     //      < > const Login: React.FC<RouteComponentProps> = () => { // useStaet  ,   useEffect - , //    ,     state const [user, setField] = React.useState<IUserIdentity>({ username: '', password: '', }) // ,    ( )  "" const [notification, setNotification] = React.useState<string>('') //  e (event) ,   <input /> //   : React.SyntheticEvent<HTMLInputElement> const onInputChange = (fieldName: string) => ( e: React.SyntheticEvent<HTMLInputElement> ): void => { setField({ ...user, [fieldName]: e.currentTarget.value, }) setNotification('') } //  e (event) ,    form //   : React.SyntheticEvent<HTMLFormElement> const onSubmit = (e: React.SyntheticEvent<HTMLFormElement>): void => { e.preventDefault() authenticate(user) .then(() => { navigate(`/profile`) //   profile }) .catch(err => { if (err.errorText) { setNotification(err.errorText) } else { // tslint:disable-next-line: no-console console.warn('request problem', err) } }) } return ( <> <h2>Login</h2> <form onSubmit={onSubmit}> {notification ? <p>{notification}</p> : null} <input type="text" value={user.username} onChange={onInputChange('username')} /> <input type="text" value={user.password} onChange={onInputChange('password')} /> <button>Login</button> </form> </> ) } export { Login } 

, 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' //  noThrow    - https://reach.tech/router/api/Redirect const Authenticated: React.FC<RouteComponentProps> = ({ children }) => { return checkAuthStatus() ? ( <React.Fragment>{children}</React.Fragment> ) : ( <Redirect to="/login" noThrow={true} /> ) } export { Authenticated } 

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' //   import { formErrors } from '../localization/formErrors' import { IUserIdentity } from '../models/user' //    const lang = 'ru' const Login: React.FC<RouteComponentProps> = () => { // ...  const onSubmit = (e: React.SyntheticEvent<HTMLFormElement>): void => { e.preventDefault() authenticate(user) .then(() => { navigate(`/profile`) }) .catch(err => { if (err.errorText) { //      (  ,  ru) setNotification(formErrors[lang][err.errorText]) } else { // tslint:disable-next-line: no-console console.warn('request problem', err) } }) } // ...  } export { Login } 

, .



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 } 

, . , "".







TypeScript. , TS . , , "one" "two" ( — union).


, — .


" " telegram youtube ( 11 2019).


Merci de votre attention! , :




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 .

Source: https://habr.com/ru/post/fr443424/


All Articles