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:
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'

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:
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
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.
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
:
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;
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'
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'
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'

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'
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;
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,
Nuestra llamada de api getNews
devuelve Promise , y esta "Promesa" tiene un cierto tipo, que también podemos describir:
interface INewsResponse { status: number;
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'
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'
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;
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'
, .
:
: . 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 }
, . , "".

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