Reescrevendo o caso de teste do front-end júnior para TypeScript e ganchos de reação

Olá Habr, hoje trabalharemos com TypeScript e React-hooks. Este tutorial o ajudará a entender o básico do "script" e a trabalhar em uma tarefa de teste para o front-end .


As tarefas de teste no projeto "Sem água" são uma oportunidade para obter uma revisão de código. O prazo para a atribuição atual é 11 de abril de 2019.



Versão em vídeo


Se você estiver com preguiça de ler, participe do seminário on-line em 20 de março às 21:00, horário de Moscou. Registro (sem e-mails e sms). Webinar realizado, gravação de webinar.


Preparação


Para começar, você pode usar a versão TypeScript Create-react-app ou usar o meu starter (que já inclui o roteador de alcance)


Vou usar meu iniciador (mais sobre isso na seção "Prática").


Teoria TypeScript


O TS resolve o problema da "digitação dinâmica" em JavaScript, quando sua variável pode assumir valores diferentes. Agora uma linha, depois um número ou mesmo um objeto. Era tão conveniente escrever no "século 19", no entanto, agora todos concordam que, se você tiver tipos predefinidos (considere regras), a base de código é mais fácil de manter. E há menos bugs no estágio de desenvolvimento.


Por exemplo, se você tiver um componente que exibe um item de notícia, podemos especificar o seguinte tipo para o item de notícia:


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

Assim, especificamos tipos "estáticos" estritos para as propriedades do nosso objeto "notícias". Se tentarmos obter uma propriedade inexistente, o TypeScript mostrará um erro.


 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 } 


Além disso, o Visual studio Code e outros editores avançados mostrarão um erro:



Visualmente, conveniente. No meu caso, o VS Code mostra dois erros ao mesmo tempo: o tipo da variável não está definido (ou seja, não existe em nossas notícias sobre "interface") e "a variável não é usada". Além disso, as variáveis ​​não utilizadas ao usar o TypeScript são destacadas em cores claras no VS Code, por padrão.


Aqui vale a pena indicar em uma linha o motivo de uma integração tão estreita do TypeScript e do VS Code: ambos os produtos são o desenvolvimento da Microsoft.


O que mais o TypeScript pode nos dizer imediatamente? Falando no contexto de variáveis, é isso. TS é muito poderoso, ele entende o que é o quê.


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


Aqui, o TypeScript jura imediatamente em uma propriedade inexistente - toUpperCase do number do tipo. E como sabemos, de fato, apenas os tipos de string têm métodos toUpperCase () .


Agora imagine, você começa a escrever o nome de alguma função, abre o colchete e o editor mostra imediatamente uma janela de ajuda pop-up, que indica quais argumentos e que tipo podem ser passados ​​para a função.


Ou imagine: você seguiu rigorosamente as recomendações e a digitação no seu projeto é à prova de balas. Além da substituição automática, você se livra do problema com valores implícitos ( undefined ) no projeto.


Prática


Reescrevemos a primeira tarefa de teste em react -hooks + TypeScript. Por enquanto, vamos omitir o Redux , caso contrário, em vez de trabalhar no " TK reiniciado nº 1 " , basta copiar tudo daqui.


Toolkit


(para quem usa o código VS )


Por conveniência, recomendo que você instale a extensão TSLint .


Para ativar a correção automática de erro do TSLint no momento do salvamento, adicione as configurações do editor:


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

Você pode acessar as configurações através do menu ou ver onde elas moram fisicamente em seu sistema operacional.


As configurações do TSLint são padrão, mais eu desabilitei uma regra.


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

A integração acabou!


Estamos escrevendo um aplicativo


Vamos nos familiarizar com coisas novas no decorrer da peça. Para começar, clone sua ramificação inicial ou sincronize com o meu código no seu código.


Todos os arquivos de script do tipo react têm a extensão .tsx .


O que é interessante no modelo inicial?


  • gancho de pré-confirmação (já discutimos isso: texto , vídeo )
  • TSLint (substituição de ESLint )
  • arquivos a serem iniciados e dependências para esta e as próximas etapas

Vamos começar com o 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, começo padrão. Vamos tentar adicionar alguma propriedade ao <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" /> } 

Temos o erro:



(se você não recebeu o erro, verifique o rigor das configurações do tsconfig.json, deve haver uma regra noImplicitAny)


Você já concluiu pela tradução do texto do erro que nossas propriedades não devem ser do tipo nenhuma . Este tipo pode ser traduzido como "qualquer coisa". Nosso projeto tem uma regra que proíbe saída implícita desse tipo.


- Inferência implícita de tipo?


Exatamente! O TypeScript é capaz de deduzir o tipo de uma variável por padrão e lida com isso também. Isso é chamado de inferência de tipo.


Um exemplo:


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

No caso de props - TS não pode determinar o tipo da variável em 100% e, portanto, diz - deixe que seja (ou seja, digite any ). Isso é feito implicitamente e é proibido pela regra noImplicitAny nas configurações do projeto (tsconfig.json)


Podemos especificar explicitamente o tipo any e o erro desaparecerá. O tipo da variável é indicado por dois pontos.


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

Feito, não há erros, o projeto funciona, mas qual é a utilidade dessa digitação quando props podem ser alguma coisa? Temos certeza de que nosso nome é uma . A regra segue disso:


Tente evitar digitar qualquer

Há momentos em que any necessária e isso é normal, mas é imediatamente um golpe abaixo da cintura por digitação estrita.


Para descrever os tipos de props , usaremos a palavra-chave da 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> ) } 

Tente alterar o tipo de name para number e ocorrerá um erro imediatamente.



Além disso, o erro também será enfatizado no VS Code (e em muitos outros editores). O erro indica que não temos uma correspondência: passamos uma string, mas esperamos um número.


Vamos props e adicionar outros props - site ao <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" /> } 

Ocorreu um erro:


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

A propriedade do site não existe no tipo IAppProps . Aqui, quero dizer imediatamente que, pelo nome do tipo, entendemos imediatamente onde procurar. Portanto, nomeie os tipos corretamente.


Antes de corrigi-lo, vamos fazer o seguinte: exclua o parágrafo que props.site .


Temos outro texto de erro:



Aqui, quero observar apenas o que o TS deduziu: site é um tipo de string (isso é sublinhado na captura de tela).


Fix:


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

Sem erros, sem problemas.


Para trabalhar com o roteamento, precisamos renderizar filhos. Vamos nos antecipar e tentar desenhar um "componente filho".


 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 jura, eles dizem isso e aquilo - as children não children descritas no IAppProps .



Obviamente, não queremos "tipificar" algumas coisas comuns, e aqui a comunidade vem em socorro, que já digitou muito antes de nós. Por exemplo, o pacote @ types / react contém toda a digitação para react.


Ao instalar este pacote (no meu exemplo, ele já está instalado), podemos usar a seguinte entrada:


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

onde <P> são os tipos de nossos props , ou seja, o registro assumirá o formato.


 React.FC<IAppProps> 

Para quem gosta de ler grandes volumes de texto, antes de praticar, posso oferecer um artigo sobre " genéricos " (aqueles <e>). Quanto ao resto, por enquanto, basta traduzirmos esta frase assim: um componente funcional que aceita <tais e tais propriedades>.


A entrada para o componente App mudará um pouco. Versão completa.


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

Vamos analisar a seguinte linha em caracteres:


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

- Por que o tipo desapareceu após props ?


- Porque, após o App - adicionado.


Registramos que a variável App será do tipo: React.FC<IAppProps> .


React.FC é um tipo de "função" e, dentro de <>, indicamos que tipo de argumento é necessário, ou seja, indicamos que nossos props serão do tipo IAppProps .


(existe o risco de mentir um pouco para você em termos de termos, mas para simplificar o exemplo, acho que está tudo bem)


Total: aprendemos a especificar o tipo de propriedades para os props transmitidos, sem perder "suas" propriedades dos componentes React.


O código fonte no momento.


Adicionar roteamento


Usaremos o roteador de alcance para ampliar nossos horizontes. Este pacote é muito semelhante ao reag-router.


Adicione a página - Notícias (Notícias), limpe o <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 } 

O aplicativo quebrou, um erro (um dos erros, pois o terminal mostra o primeiro):



Já estamos acostumados a esse registro um pouco e entendemos que esse path não existe na descrição do tipo para <App /> .


Novamente, tudo está descrito diante de nós. Usaremos o pacote @ types / reach__router e o tipo RouteComponentProps . Para não perder nossas propriedades, usaremos a 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; } // ...    

Para os curiosos, que tipos são descritos em RouteComponentProps .


O erro em <App /> desapareceu, mas permaneceu em <News /> , pois não especificamos a digitação para este componente.


Mini quebra-cabeça: especifique a digitação para <News /> . No momento, apenas as propriedades do roteador são transferidas para lá.


A resposta é:


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 } 


Seguimos em frente e adicionamos uma rota com um parâmetro. Os parâmetros no roteador de alcance vivem diretamente em adereços. No react-router, como você se lembra, eles vivem em 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 / páginas / 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 } 

Um erro que não esperávamos:



A propriedade source não existe ... Por um lado, perplexidade: passamos para o caminho, que é string, por outro, alegria: Ah, bem, como os autores da biblioteca e a digitação tentaram nos adicionar esse aviso.


Para corrigi-lo, usaremos uma das opções : estender de RouteComponentProps e especificar a propriedade de source opcional. Opcional, porque pode não estar no nosso URL.


O TypeScript usa um ponto de interrogação para indicar uma propriedade opcional.


src / páginas / 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 (ao mesmo tempo, russificaremos a navegação)


 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 : aprendemos a tipificar os componentes envolvidos na rotação.


O código fonte no momento.




Vamos trabalhar com ganchos e continuar digitando


Lembro que nossa tarefa é implementar uma tarefa de teste , mas sem o Redux.


Eu preparei uma ramificação para iniciar esta etapa com roteamento, um formulário de login que não funciona e as páginas necessárias.



Download de notícias


Notícias é uma variedade de objetos.


Apresente nossas novidades:


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

Vamos escrever um modelo imediatamente (tipos de notícias):


src / models / news.ts (extensão .ts )


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

No novo registro de data e hora, indicava o tipo Date .


Imagine nossa chamada de dados:


 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 } 

Nossa ligação da api getNews retorna Promise , e essa "Promise" tem um certo tipo, que também podemos descrever:


 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á quente Agora ficará ainda mais quente, já que o tipo Promise é genérico, teremos que lidar novamente com < e > . Esta é a parte mais difícil do tutorial, então lemos o código 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> } 

Pausa para fumar.


Mostrar notícias


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 } 

O código fornece comentários relacionados ao TypeScript. Se precisar de ajuda com os ganchos de reação, você pode ler aqui: documentação (EN), tutorial (RU).


Tarefa: escreva um componente <NewsItem /> que exibirá notícias. Lembre-se de especificar o tipo correto. Use o modelo INewsItem .


O resultado pode ser assim:



A solução está abaixo.


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 } 

A questão é por que descrevemos a interface dessa maneira (comentários nos códigos [1] e [2]). Poderíamos simplesmente escrever:


 React.FC<INewsItem> 

A resposta está abaixo.


.


.


.


Poderia, já que estamos passando as notícias na propriedade data , ou seja, devemos escrever:


 React.FC<{ data: INewsItem }> 

O que fizemos, apenas com a diferença que especificamos interface , caso outras propriedades fossem adicionadas ao componente. E a legibilidade é melhor.


Total: praticamos a digitação para Promise e useEffect. Descreveu o tipo para outro componente.


Se você ainda não bate palmas contra a substituição automática e o rigor do TS, não pratica ou a digitação estrita não é para você. No segundo caso - não posso ajudar em nada, isso é uma questão de gosto. Chamo a atenção para o fato de que o mercado dita suas condições e mais e mais projetos convivem com a digitação, se não com o TypeScript, e com o fluxo .


O código fonte no momento.




API de autenticação


Escreva api para login. O princípio é semelhante - retornamos uma promise com a resposta autorizada / erro. Armazenaremos informações sobre o status da autorização no localStorage ( muitos consideram isso uma violação flagrante da segurança, estou um pouco atrás das disputas e não sei como terminou ).


Em nossa aplicação, o login é um monte de nome de usuário e senha (ambos são string), descreveremos o modelo:


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


Obrigado pela atenção! , :




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/pt443424/


All Articles