Reescribiendo el caso de prueba para frontend junior a TypeScript y react-hooks

Hola Habr, hoy trabajaremos con TypeScript y React-hooks. Este tutorial lo ayudará a comprender los conceptos básicos del "script" y lo ayudará a trabajar en una tarea de prueba para el front-end .


Las tareas de prueba en el proyecto "Sin agua" son una oportunidad para obtener una revisión del código. La fecha límite para la asignación actual es el 11 de abril de 2019.



Versión de video


Si es demasiado flojo para leer, venga al seminario web el 20 de marzo a las 21:00 hora de Moscú. Registro (sin correos electrónicos y sms). Seminario web realizado, grabación de seminario web.


Preparación


Para comenzar, puede tomar la versión de TypeScript Create-react-app o usar mi iniciador (que ya incluye el router de alcance)


Usaré mi iniciador (más sobre esto en la sección "Práctica").


Teoría TypeScript


TS resuelve el problema del "tipeo dinámico" en JavaScript, cuando su variable puede tomar diferentes valores. Ahora una línea, luego un número, o incluso un objeto. Era tan conveniente escribir en el "siglo XIX", sin embargo, ahora todos están de acuerdo en que si tiene tipos predefinidos (considere las reglas), entonces la base del código es más fácil de mantener. Y hay menos errores en la etapa de desarrollo.


Por ejemplo, si tiene un componente que muestra una noticia, podemos especificar el siguiente tipo para la noticia:


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

Por lo tanto, hemos especificado tipos estrictos "estáticos" para las propiedades de nuestro objeto "noticias". Si intentamos obtener una propiedad inexistente, TypeScript mostrará un error.


 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 } 


Además, Visual Studio Code y otros editores avanzados le mostrarán un error:



Visualmente, conveniente. En mi caso, VS Code muestra dos errores a la vez: el tipo de la variable no está establecida (es decir, no existe en las noticias de nuestra "interfaz") y "la variable no se usa". Además, las variables no utilizadas cuando se usa TypeScript se resaltan en color pálido en el Código VS de forma predeterminada.


Aquí vale la pena indicar en una línea la razón de una integración tan estrecha de TypeScript y VS Code: ambos productos son el desarrollo de Microsoft.


¿Qué más puede decirnos TypeScript de inmediato? Hablando en el contexto de variables, eso es todo. TS es muy poderoso, entiende lo que es 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> ) } 


Aquí, TypeScript jura inmediatamente una propiedad inexistente: toUpperCase del number de tipo. Y como sabemos, de hecho, solo los tipos de cadenas tienen métodos toUpperCase () .


Ahora imagine que comienza a escribir el nombre de alguna función, abre el paréntesis y el editor le muestra inmediatamente una ventana emergente de ayuda, que indica qué argumentos y qué tipo se pueden pasar a la función.


O imagine: siguió estrictamente las recomendaciones y escribir en su proyecto es a prueba de balas. Además de la sustitución automática, elimina el problema con valores implícitos ( undefined ) en el proyecto.


Practica


Reescribimos la primera tarea de prueba en react-hooks + TypeScript. Por ahora, omita Redux , de lo contrario, en lugar de trabajar en " reiniciado TK # 1 ", simplemente copie todo desde aquí.


Kit de herramientas


(para aquellos que usan VS Code )


Para su comodidad, le recomiendo que instale la extensión TSLint .


Para habilitar la corrección automática de errores de TSLint al momento de guardar, agregue en la configuración del editor:


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

Puede acceder a la configuración a través del menú o ver dónde viven físicamente en su sistema operativo.


La configuración de TSLint es estándar, además deshabilité una regla.


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

¡La integración ha terminado!


Estamos escribiendo una solicitud


Nos familiarizaremos con cosas nuevas para nosotros en el transcurso de la obra. Para comenzar, clone su rama de 1 inicio o sincronice con mi código en su código.


Todos los archivos de script de tipo react que tenemos tienen la extensión .tsx .


¿Qué es interesante en la plantilla de inicio?


  • gancho previo al compromiso (ya discutimos esto: texto , video )
  • TSLint (reemplazo de ESLint )
  • archivos para iniciar y dependencias para este y los próximos pasos

Comencemos con 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, inicio estándar. Intentemos agregar alguna propiedad a <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" /> } 

Obtenemos el error:



(si no recibió el error, compruebe la rigurosidad de la configuración de tsconfig.json, debería haber una regla noImplicitAny)


Usted ya adivina por la traducción del texto de error que nuestras propiedades no deberían ser de ningún tipo. Este tipo se puede traducir como "cualquier cosa". Nuestro proyecto tiene una regla que prohíbe la salida implícita de este tipo.


- ¿Inferencia de tipo implícita?


- Exactamente! TypeScript es capaz de deducir el tipo de una variable por defecto y se las arregla bien. Esto se llama inferencia de tipo.


Un ejemplo:


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

En el caso de los props , TS no puede determinar el tipo de la variable en un 100% y, por lo tanto, dice: que sea (es decir, escriba any ). Esto se hace implícitamente y está prohibido por la regla noImplicitAny en la configuración del proyecto (tsconfig.json)


Podemos especificar explícitamente el tipo any y el error desaparecerá. El tipo de la variable se indica con dos puntos.


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

Hecho, no hay errores, el proyecto funciona, pero ¿de qué sirve escribir así cuando los props pueden ser algo? Sabemos con certeza que nuestro nombre es una . La regla se desprende de esto:


Intenta evitar escribir cualquier

Hay momentos en que any necesario y esto es normal, pero es un golpe inmediato debajo de la cintura al escribir estrictamente.


Para describir los tipos de props , usaremos la palabra clave de 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> ) } 

Intente cambiar el tipo de name a number y se producirá un error de inmediato.



Además, el error también se enfatizará en VS Code (y en muchos otros editores). El error indica que no tenemos una coincidencia: pasamos una cadena, pero esperamos un número.


Lo props y agregaremos otros props : sitio a <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" /> } 

Tengo un error:


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

La propiedad del site no existe en el tipo IAppProps . Aquí, quiero decir de inmediato que por el nombre del tipo entendemos de inmediato dónde buscar. Por lo tanto, nombre los tipos correctamente.


Antes de solucionarlo, hagamos esto: elimine el párrafo que props.site .


Recibimos otro texto de error:



Aquí quiero señalar solo lo que TS dedujo: el site es un tipo de string (esto está subrayado en la captura de pantalla).


Arreglo:


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

Sin errores, sin problemas.


Para trabajar con enrutamiento, necesitamos renderizado de niños. Vamos a adelantarnos e intentar dibujar un "componente hijo".


 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, dicen que sí, que los children no se describen en IAppProps .



Por supuesto, no quisiéramos "tipificar" algunas cosas estándar, y aquí la comunidad viene al rescate, que ya ha escrito mucho antes que nosotros. Por ejemplo, el paquete @ types / react contiene todo el tipeo de react.


Al instalar este paquete (en mi ejemplo, ya está instalado), podemos usar la siguiente entrada:


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

donde <P> son los tipos de nuestros props , es decir, el registro tomará la forma.


 React.FC<IAppProps> 

Para aquellos que gustan de leer grandes volúmenes de texto, antes de practicar, puedo ofrecer un artículo sobre " genéricos " (esos <y>). Por lo demás, por ahora, es suficiente que traduzcamos esta frase así: un componente funcional que acepta <tales y tales propiedades>.


La entrada para el componente Aplicación cambiará ligeramente. Versión 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> ) } 

Analicemos la siguiente línea en caracteres:


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

- ¿Por qué desapareció el tipo después de los props ?


- Porque, después de la App - agregó.


Registramos que la variable de la aplicación será del tipo: React.FC<IAppProps> .


React.FC es un tipo de "función", y dentro de <> indicamos qué tipo de argumento se necesita, es decir, indicamos que nuestros props serán del tipo IAppProps .


(existe el riesgo de que te haya mentido un poco en términos, pero para simplificar el ejemplo, creo que está bien)


Total: aprendimos a especificar el tipo de propiedades para los props transmitidos, sin perder "sus" propiedades de los componentes React.


El código fuente en este momento.


Agregar enrutamiento


Usaremos el enrutador de alcance para ampliar nuestros horizontes. Este paquete es muy similar al react-router.


Agregue la página - Noticias (Noticias), limpie la <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 } 

La aplicación se ha roto, un error (uno de los errores, ya que el terminal muestra el primero):



Ya estamos acostumbrados a este registro un poco, y entendemos que la path no existe en la descripción del tipo para <App /> .


Nuevamente, todo se describe ante nosotros. Utilizaremos el paquete @ types / reach__router y el tipo RouteComponentProps . Para no perder nuestras propiedades, utilizaremos la 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 los curiosos, qué tipos se describen en RouteComponentProps .


El error en <App /> desapareció, pero permaneció en <News /> , ya que no especificamos la escritura para este componente.


Mini rompecabezas: especifique el tipo para <News /> . Por el momento, solo las propiedades del enrutador se transfieren allí.


La respuesta es:


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 adelante y agregamos una ruta con un parámetro. Los parámetros en el enrutador de alcance viven directamente en accesorios. En react-router, como recordarán, viven en 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 } 

Un error que no esperábamos:



La propiedad fuente no existe ... Por un lado, desconcierto: lo pasamos a la ruta, que es cadena, por otro lado, alegría: Ah, bueno, ¿cómo los autores de la biblioteca y la escritura intentaron agregarnos esta advertencia?


Para solucionarlo, utilizaremos una de las opciones : extender desde RouteComponentProps y especificar la propiedad source opcional. Opcional, porque puede no estar en nuestra URL.


TypeScript utiliza un signo de interrogación para indicar una propiedad opcional.


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 (al mismo tiempo vamos a rusificar la navegación)


 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 : aprendimos a tipificar los componentes involucrados en la rotación.


El código fuente en este momento.




Trabajemos con ganchos y sigamos escribiendo


Les recuerdo que nuestra tarea es implementar una tarea de prueba , pero sin Redux.


Preparé una rama para comenzar este paso con el enrutamiento, un formulario de inicio de sesión que no funciona y las páginas necesarias.



Descargar noticias


Las noticias son una variedad de objetos.


Presente nuestras noticias:


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

Escribamos un modelo de inmediato (tipos para noticias):


src / models / news.ts (extensión .ts )


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

Desde la nueva marca de tiempo indica el tipo Date .


Imagine nuestra llamada de datos:


 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 } 

Nuestra llamada de api getNews devuelve Promise , y esta "Promesa" tiene un cierto tipo, que también podemos describir:


 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; } //   __[] -      __ // [{__}, {__}, {__}] 

Hace calor Ahora será aún más caliente, ya que el tipo Promise es genérico, nuevamente tendremos que tratar con < y > . Esta es la parte más difícil del tutorial, por lo que leemos el 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 noticias


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 } 

El código da comentarios relacionados con TypeScript. Si necesita ayuda con react-hooks, puede leer aquí: documentación (EN), tutorial (RU).


Tarea: escriba un <NewsItem /> que muestre noticias. Recuerde especificar el tipo correcto. Use el modelo INewsItem .


El resultado puede verse así:



La solución está abajo.


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 pregunta es por qué describimos la interfaz de esta manera (comentarios en el código [1] y [2]). ¿Podríamos simplemente escribir:


 React.FC<INewsItem> 

La respuesta está abajo.


.


.


.


Podría, ya que estamos pasando las noticias en la propiedad de data , es decir, debemos escribir:


 React.FC<{ data: INewsItem }> 

Lo cual hicimos, solo con la diferencia que especificamos en la interface , en caso de que se agregaran otras propiedades al componente. Y la legibilidad es mejor.


Total: practicamos escribir para Promise y useEffect. Describió el tipo para otro componente.


Si todavía no aplaude con la sustitución automática y la rigidez de TS, entonces no practique o la mecanografía estricta no es para usted. En el segundo caso, no puedo ayudar con nada, es cuestión de gustos. Le llamo la atención sobre el hecho de que el mercado dicta sus condiciones y cada vez más proyectos viven con mecanografía, si no con TypeScript, luego con flujo .


El código fuente en este momento.




API de autenticación


Escribe api para iniciar sesión. El principio es similar: devolvemos una promise con la respuesta autorizada / error. Almacenaremos información sobre el estado de autorización en localStorage ( muchos consideran que esto es una violación flagrante de la seguridad, estoy un poco detrás de las disputas y no sé cómo terminó ).


En nuestra aplicación, el inicio de sesión es un grupo de nombre de usuario y contraseña (ambos son cadenas), describiremos el 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 / 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' //    ,     //      < > 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 } 

, . , "".



→




Conclusión


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


, — .


" " telegram youtube ( 11 2019).


Gracias por su atencion! , :




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


All Articles