Escritura estática dinámica en TypeScript

Publicado: 2022-03-10
Resumen rápido ↬ En este artículo, analizamos algunas de las características más avanzadas de TypeScript, como tipos de unión, tipos condicionales, tipos de plantilla literal y genéricos. Queremos formalizar el comportamiento de JavaScript más dinámico de manera que podamos detectar la mayoría de los errores antes de que sucedan. Aplicamos varios aprendizajes de todos los capítulos de TypeScript en 50 lecciones, un libro que publicamos aquí en Smashing Magazine a fines de 2020. Si está interesado en aprender más, ¡asegúrese de consultarlo!

JavaScript es un lenguaje de programación inherentemente dinámico. Nosotros, como desarrolladores, podemos expresar mucho con poco esfuerzo, y el lenguaje y su tiempo de ejecución determinan lo que pretendíamos hacer. ¡Esto es lo que hace que JavaScript sea tan popular entre los principiantes y lo que hace que los desarrolladores experimentados sean productivos! Sin embargo, hay una advertencia: ¡debemos estar alerta! Errores, errores tipográficos, comportamiento correcto del programa: ¡Mucho de eso sucede en nuestras cabezas!

Echa un vistazo al siguiente ejemplo.

 app.get("/api/users/:userID", function(req, res) { if (req.method === "POST") { res.status(20).send({ message: "Got you, user " + req.params.userId }); } })

Tenemos un servidor de estilo https://expressjs.com/ que nos permite definir una ruta (o ruta) y ejecuta una devolución de llamada si se solicita la URL.

La devolución de llamada toma dos argumentos:

  1. El objeto de la request .
    Aquí obtenemos información sobre el método HTTP utilizado (p. ej., GET, POST, PUT, DELETE) y parámetros adicionales que entran. En este ejemplo, el ID de usuario debe userID a un parámetro userID de usuario que, bueno, contiene el ID del usuario.
  2. La response o el objeto de reply .
    Aquí queremos preparar una respuesta adecuada del servidor al cliente. Queremos enviar los códigos de estado correctos ( status del método) y enviar la salida JSON por cable.

Lo que vemos en este ejemplo está muy simplificado, pero da una buena idea de lo que estamos haciendo. ¡El ejemplo anterior también está plagado de errores! Echar un vistazo:

 app.get("/api/users/:userID", function(req, res) { if (req.method === "POST") { /* Error 1 */ res.status(20).send({ /* Error 2 */ message: "Welcome, user " + req.params.userId /* Error 3 */ }); } })

¡Oh wow! ¿Tres líneas de código de implementación y tres errores? ¿Lo que ha sucedido?

  1. El primer error tiene matices. Si bien le decimos a nuestra aplicación que queremos escuchar las solicitudes GET (por lo tanto, app.get ), solo hacemos algo si el método de solicitud es POST . En este punto particular de nuestra aplicación, req.method no puede ser POST . Por lo tanto, nunca enviaríamos ninguna respuesta, lo que podría provocar tiempos de espera inesperados.
  2. ¡Genial que enviemos explícitamente un código de estado! Sin embargo, 20 no es un código de estado válido. Es posible que los clientes no entiendan lo que está sucediendo aquí.
  3. Esta es la respuesta que queremos enviar de vuelta. Accedemos a los argumentos analizados pero tenemos un error tipográfico medio. Es userID de usuario, no userId de usuario. Todos nuestros usuarios serían recibidos con “¡Bienvenido, usuario indefinido!”. ¡Algo que definitivamente has visto en la naturaleza!

¡Y cosas así pasan! Especialmente en JavaScript. Ganamos en expresividad, ni una sola vez tuvimos que preocuparnos por los tipos, sino que debemos prestar mucha atención a lo que estamos haciendo.

Aquí también es donde JavaScript recibe muchas críticas de los programadores que no están acostumbrados a los lenguajes de programación dinámicos. Por lo general, tienen compiladores que les señalan posibles problemas y detectan errores por adelantado. Pueden parecer presumidos cuando fruncen el ceño ante la cantidad de trabajo adicional que tienes que hacer en tu cabeza para asegurarte de que todo funcione bien. Incluso podrían decirle que JavaScript no tiene tipos. Lo cual no es cierto.

Anders Hejlsberg, el arquitecto principal de TypeScript, dijo en su discurso de apertura de MS Build 2017 que “ no es que JavaScript no tenga un sistema de tipos. Simplemente no hay manera de formalizarlo ”.

Y este es el propósito principal de TypeScript. TypeScript quiere comprender su código JavaScript mejor que usted. Y cuando TypeScript no pueda descifrar lo que quiere decir, puede ayudar brindando información de tipo adicional.

¡Más después del salto! Continúe leyendo a continuación ↓

Escritura básica

Y esto es lo que vamos a hacer ahora. Tomemos el método get de nuestro servidor de estilo Express y agreguemos suficiente información de tipo para que podamos excluir tantas categorías de errores como sea posible.

Comenzamos con alguna información de tipo básico. Tenemos un objeto de app que apunta a una función de get . La función get toma path , que es una cadena, y una devolución de llamada.

 const app = { get, /* post, put, delete, ... to come! */ }; function get(path: string, callback: CallbackFn) { // to be implemented --> not important right now }

Mientras que string es un tipo básico, llamado primitivo , CallbackFn es un tipo compuesto que tenemos que definir explícitamente.

CallbackFn es un tipo de función que toma dos argumentos:

  • req , que es de tipo ServerRequest
  • reply que es de tipo ServerReply

CallbackFn devuelve void .

 type CallbackFn = (req: ServerRequest, reply: ServerReply) => void;

ServerRequest es un objeto bastante complejo en la mayoría de los marcos. Hacemos una versión simplificada con fines de demostración. Pasamos una cadena de method , para "GET" , "POST" , "PUT" , "DELETE" , etc. También tiene un registro de params . Los registros son objetos que asocian un conjunto de claves con un conjunto de propiedades. Por ahora, queremos permitir que cada clave de string se asigne a una propiedad de string . Refactorizaremos este más tarde.

 type ServerRequest = { method: string; params: Record<string, string>; };

Para ServerReply , presentamos algunas funciones, sabiendo que un objeto ServerReply real tiene mucho más. Una función de send toma un argumento opcional con los datos que queremos enviar. Y tenemos la posibilidad de establecer un código de estado con la función de status .

 type ServerReply = { send: (obj?: any) => void; status: (statusCode: number) => ServerReply; };

Eso ya es algo, y podemos descartar un par de errores:

 app.get("/api/users/:userID", function(req, res) { if(req.method === 2) { // ^^^^^^^^^^^^^^^^^ Error, type number is not assignable to string res.status("200").send() // ^^^^^ Error, type string is not assignable to number } })

Pero aún podemos enviar códigos de estado incorrectos (cualquier número es posible) y no tenemos idea de los posibles métodos HTTP (cualquier cadena es posible). Vamos a refinar nuestros tipos.

Conjuntos más pequeños

Puede ver los tipos primitivos como un conjunto de todos los valores posibles de esa categoría determinada. Por ejemplo, string incluye todas las cadenas posibles que se pueden expresar en JavaScript, number incluye todos los números posibles con precisión de doble flotante. boolean incluye todos los valores booleanos posibles, que son true y false .

TypeScript le permite refinar esos conjuntos a subconjuntos más pequeños. Por ejemplo, podemos crear un Method de tipo que incluya todas las cadenas posibles que podemos recibir para los métodos HTTP:

 type Methods= "GET" | "POST" | "PUT" | "DELETE"; type ServerRequest = { method: Methods; params: Record<string, string>; };

El Method es un conjunto más pequeño del conjunto de string más grande. Method es un tipo de unión de tipos literales. Un tipo literal es la unidad más pequeña de un conjunto dado. Una cadena literal. Un número literal. No hay ambigüedad. Es simplemente "GET" . Los pones en una unión con otros tipos literales, creando un subconjunto de cualquier tipo más grande que tengas. También puede hacer un subconjunto con tipos literales de string y number , o diferentes tipos de objetos compuestos. Hay muchas posibilidades para combinar y poner tipos literales en uniones.

Esto tiene un efecto inmediato en la devolución de llamada de nuestro servidor. De repente, podemos diferenciar entre esos cuatro métodos (o más si es necesario), y podemos agotar todas las posibilidades en el código. TypeScript nos guiará:

 app.get("/api/users/:userID", function (req, res) { // at this point, TypeScript knows that req.method // can take one of four possible values switch (req.method) { case "GET": break; case "POST": break; case "DELETE": break; case "PUT": break; default: // here, req.method is never req.method; } });

Con cada declaración de case que haga, TypeScript puede brindarle información sobre las opciones disponibles. Pruébelo usted mismo. Si agotó todas las opciones, TypeScript le dirá en su rama default que esto never puede suceder. Este es literalmente el tipo never , lo que significa que posiblemente haya llegado a un estado de error que debe manejar.

Esa es una categoría de errores menos. Ahora sabemos exactamente qué posibles métodos HTTP están disponibles.

Podemos hacer lo mismo con los códigos de estado HTTP, definiendo un subconjunto de números válidos que puede tomar statusCode :

 type StatusCode = 100 | 101 | 102 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 226 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 420 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 444 | 449 | 450 | 451 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 598 | 599; type ServerReply = { send: (obj?: any) => void; status: (statusCode: StatusCode) => ServerReply; };

Type StatusCode es nuevamente un tipo de unión. Y con eso, excluimos otra categoría de errores. De repente, un código como ese falla:

 app.get("/api/user/:userID", (req, res) => { if(req.method === "POS") { // ^^^^^^^^^^^^^^^^^^^ 'Methods' and '"POS"' have no overlap. res.status(20) // ^^ '20' is not assignable to parameter of type 'StatusCode' } })
¡Y nuestro software se vuelve mucho más seguro! ¡Pero podemos hacer más!

Introducir genéricos

Cuando definimos una ruta con app.get , implícitamente sabemos que el único método HTTP posible es "GET" . Pero con nuestras definiciones de tipo, todavía tenemos que verificar todas las partes posibles de la unión.

El tipo de CallbackFn es correcto, ya que podríamos definir funciones de devolución de llamada para todos los métodos HTTP posibles, pero si llamamos explícitamente a app.get , sería bueno ahorrar algunos pasos adicionales que solo son necesarios para cumplir con los tipos.

¡Los genéricos de TypeScript pueden ayudar! Los genéricos son una de las características principales de TypeScript que le permiten obtener el comportamiento más dinámico de los tipos estáticos. En TypeScript en 50 lecciones, dedicamos los últimos tres capítulos a profundizar en todas las complejidades de los genéricos y su funcionalidad única.

Lo que necesita saber en este momento es que queremos definir ServerRequest de manera que podamos especificar una parte de los Methods en lugar del conjunto completo. Para eso, usamos la sintaxis genérica donde podemos definir parámetros como lo haríamos con las funciones:

 type ServerRequest<Met extends Methods> = { method: Met; params: Record<string, string>; };

Esto es lo que pasa:

  1. ServerRequest se convierte en un tipo genérico, como lo indican los corchetes angulares
  2. Definimos un parámetro genérico llamado Met , que es un subconjunto de Methods de tipo
  3. Usamos este parámetro genérico como una variable genérica para definir el método.

También le animo a que consulte mi artículo sobre la denominación de parámetros genéricos.

Con ese cambio, podemos especificar diferentes ServerRequest sin duplicar cosas:

 type OnlyGET = ServerRequest<"GET">; type OnlyPOST = ServerRequest<"POST">; type POSTorPUT = ServerRquest<"POST" | "PUT">;

Como cambiamos la interfaz de ServerRequest , tenemos que hacer cambios en todos nuestros otros tipos que usan ServerRequest , como CallbackFn y la función get :

 type CallbackFn<Met extends Methods> = ( req: ServerRequest<Met>, reply: ServerReply ) => void; function get(path: string, callback: CallbackFn<"GET">) { // to be implemented }

Con la función get , pasamos un argumento real a nuestro tipo genérico. Sabemos que esto no será solo un subconjunto de Methods , sabemos exactamente con qué subconjunto estamos tratando.

Ahora, cuando usamos app.get , solo tenemos un valor posible para req.method :

 app.get("/api/users/:userID", function (req, res) { req.method; // can only be get });

Esto garantiza que no asumamos que los métodos HTTP como "POST" o similares están disponibles cuando creamos una devolución de llamada app.get . Sabemos exactamente a lo que nos enfrentamos en este punto, así que reflejémoslo en nuestros tipos.

Ya hicimos mucho para asegurarnos de que request.method se escriba razonablemente y represente el estado real de las cosas. Un buen beneficio que obtenemos al crear un subconjunto del tipo de unión Methods es que podemos crear una función de devolución de llamada de propósito general fuera de app.get que sea de tipo seguro:

 const handler: CallbackFn<"PUT" | "POST"> = function(res, req) { res.method // can be "POST" or "PUT" }; const handlerForAllMethods: CallbackFn<Methods> = function(res, req) { res.method // can be all methods }; app.get("/api", handler); // ^^^^^^^ Nope, we don't handle "GET" app.get("/api", handlerForAllMethods); // This works

Parámetros de escritura

Lo que aún no hemos tocado es escribir el objeto params . Hasta ahora, tenemos un registro que permite acceder a cada clave string . ¡Es nuestra tarea ahora hacer eso un poco más específico!

Hacemos eso agregando otra variable genérica. Uno para los métodos, otro para las posibles claves en nuestro Record :

 type ServerRequest<Met extends Methods, Par extends string = string> = { method: Met; params: Record<Par, string>; };

La variable de tipo genérico Par puede ser un subconjunto del tipo string y el valor predeterminado es cada cadena. Con eso, podemos decirle a ServerRequest qué claves esperamos:

 // request.method = "GET" // request.params = { // userID: string // } type WithUserID = ServerRequest<"GET", "userID">

Agreguemos el nuevo argumento a nuestra función get y el tipo CallbackFn , para que podamos establecer los parámetros solicitados:

 function get<Par extends string = string>( path: string, callback: CallbackFn<"GET", Par> ) { // to be implemented } type CallbackFn<Met extends Methods, Par extends string> = ( req: ServerRequest<Met, Par>, reply: ServerReply ) => void;

Si no establecemos Par explícitamente, el tipo funciona como estamos acostumbrados, ya que Par está predeterminado en string . Sin embargo, si lo configuramos, de repente tenemos una definición adecuada para el objeto req.params .

 app.get<"userID">("/api/users/:userID", function (req, res) { req.params.userID; // Works!! req.params.anythingElse; // doesn't work!! });

¡Eso es genial! Sin embargo, hay una pequeña cosa que se puede mejorar. Todavía podemos pasar cada cadena al argumento de path de app.get . ¿No sería mejor si pudiéramos reflejar a Par allí también?

¡Podemos! Con el lanzamiento de la versión 4.1, TypeScript puede crear tipos de literales de plantilla . Sintácticamente, funcionan como literales de plantilla de cadena, pero en un nivel de tipo. Donde pudimos dividir la string establecida en subconjuntos con tipos de literales de cadena (como hicimos con Métodos), los tipos de literales de plantilla nos permiten incluir un espectro completo de cadenas.

Vamos a crear un tipo llamado IncludesRouteParams , donde queremos asegurarnos de que Par esté correctamente incluido en la forma Express de agregar dos puntos delante del nombre del parámetro:

 type IncludesRouteParams<Par extends string> = | `${string}/:${Par}` | `${string}/:${Par}/${string}`;

El tipo genérico IncludesRouteParams toma un argumento, que es un subconjunto de string . Crea un tipo de unión de dos literales de plantilla:

  1. El primer literal de la plantilla comienza con cualquier string , luego incluye un carácter / seguido de un carácter : , seguido del nombre del parámetro. Esto asegura que capturamos todos los casos en los que el parámetro está al final de la cadena de ruta.
  2. El segundo literal de la plantilla comienza con cualquier string , seguido del mismo patrón de / : y el nombre del parámetro. Luego tenemos otro carácter / , seguido de cualquier cadena. Esta rama del tipo de unión se asegura de que atrapemos todos los casos en los que el parámetro está en algún lugar dentro de una ruta.

Así es como se comporta el nombre de parámetro " IncludesRouteParams " con userID de usuario en diferentes casos de prueba:

 const a: IncludeRouteParams<"userID"> = "/api/user/:userID" // const a: IncludeRouteParams<"userID"> = "/api/user/:userID/orders" // const a: IncludeRouteParams<"userID"> = "/api/user/:userId" // const a: IncludeRouteParams<"userID"> = "/api/user" // const a: IncludeRouteParams<"userID"> = "/api/user/:userIDAndmore" //

Incluyamos nuestro nuevo tipo de utilidad en la declaración de la función get .

 function get<Par extends string = string>( path: IncludesRouteParams<Par>, callback: CallbackFn<"GET", Par> ) { // to be implemented } app.get<"userID">( "/api/users/:userID", function (req, res) { req.params.userID; // YEAH! } );

¡Genial! ¡Obtenemos otro mecanismo de seguridad para asegurarnos de no perder la oportunidad de agregar los parámetros a la ruta real! Que poderoso

Encuadernaciones genéricas

Pero adivina qué, todavía no estoy contento con eso. Hay algunos problemas con ese enfoque que se vuelven evidentes en el momento en que sus rutas se vuelven un poco más complejas.

  1. El primer problema que tengo es que necesitamos indicar explícitamente nuestros parámetros en el parámetro de tipo genérico. Tenemos que vincular Par a "userID" , aunque lo especificaríamos de todos modos en el argumento de ruta de la función. ¡Esto no es JavaScript-y!
  2. Este enfoque solo maneja un parámetro de ruta. En el momento en que agregamos una unión, por ejemplo, "userID" | "orderId" "userID" | "orderId" la verificación a prueba de fallas se cumple con solo uno de esos argumentos disponibles. Así es como funcionan los conjuntos. Puede ser uno, o el otro.

Tiene que haber una mejor manera. Y ahí está. De lo contrario, este artículo terminaría con una nota muy amarga.

¡Invirtamos el orden! No intentemos definir los parámetros de ruta en una variable de tipo genérico, sino extraer las variables de la path que pasamos como el primer argumento de app.get .

Para llegar al valor real, tenemos que ver cómo funciona el enlace genérico en TypeScript. Tomemos esta función de identity por ejemplo:

 function identity<T>(inp: T) : T { return inp }

Puede que sea la función genérica más aburrida que jamás hayas visto, pero ilustra perfectamente un punto. La identity toma un argumento y devuelve la misma entrada nuevamente. El tipo es el tipo genérico T y también devuelve el mismo tipo.

Ahora podemos vincular T a string , por ejemplo:

 const z = identity<string>("yes"); // z is of type string

Esta vinculación explícitamente genérica garantiza que solo pasemos strings a la identity y, dado que vinculamos explícitamente, el tipo de retorno también es una string . Si nos olvidamos de enlazar, sucede algo interesante:

 const y = identity("yes") // y is of type "yes"

En ese caso, TypeScript infiere el tipo del argumento que pasa y vincula T al tipo de cadena literal "yes" . Esta es una excelente manera de convertir un argumento de función en un tipo literal, que luego usamos en nuestros otros tipos genéricos.

Hagámoslo adaptando app.get .

 function get<Path extends string = string>( path: Path, callback: CallbackFn<"GET", ParseRouteParams<Path>> ) { // to be implemented }

Eliminamos el tipo genérico Par y añadimos Path . Path puede ser un subconjunto de cualquier string . Establecemos la path a este tipo genérico Path , lo que significa que en el momento en que pasamos un parámetro para get , capturamos su tipo literal de cadena. Pasamos Path a un nuevo tipo genérico ParseRouteParams que aún no hemos creado.

Trabajemos en ParseRouteParams . Aquí, volvemos a cambiar el orden de los eventos. En lugar de pasar los parámetros de ruta solicitados al genérico para asegurarnos de que la ruta esté bien, pasamos la ruta de ruta y extraemos los posibles parámetros de ruta. Para eso, necesitamos crear un tipo condicional.

Tipos condicionales y tipos literales de plantillas recursivas

Los tipos condicionales son sintácticamente similares al operador ternario en JavaScript. Verifica una condición y, si se cumple la condición, devuelve la rama A; de lo contrario, devuelve la rama B. Por ejemplo:

 type ParseRouteParams<Rte> = Rte extends `${string}/:${infer P}` ? P : never;

Aquí, verificamos si Rte es un subconjunto de cada ruta que termina con el parámetro al final Express-style (con un "/:" precedente). Si es así, inferimos esta cadena. Lo que significa que capturamos su contenido en una nueva variable. Si la condición se cumple, devolvemos la cadena recién extraída, de lo contrario, devolvemos nunca, como en: "No hay parámetros de ruta",

Si lo probamos, obtenemos algo como esto:

 type Params = ParseRouteParams<"/api/user/:userID"> // Params is "userID" type NoParams = ParseRouteParams<"/api/user"> // NoParams is never --> no params!

Genial, eso ya es mucho mejor que lo que hicimos antes. Ahora, queremos capturar todos los demás parámetros posibles. Para eso, tenemos que añadir otra condición:

 type ParseRouteParams<Rte> = Rte extends `${string}/:${infer P}/${infer Rest}` ? P | ParseRouteParams<`/${Rest}`> : Rte extends `${string}/:${infer P}` ? P : never;

Nuestro tipo condicional funciona ahora de la siguiente manera:

  1. En la primera condición, verificamos si hay un parámetro de ruta en algún lugar entre la ruta. Si es así, extraemos tanto el parámetro de ruta como todo lo que viene después. Devolvemos el parámetro de ruta P recién encontrado en una unión donde llamamos al mismo tipo genérico recursivamente con Rest . Por ejemplo, si pasamos la ruta "/api/users/:userID/orders/:orderID" a ParseRouteParams , inferimos "userID" en P y "orders/:orderID" en Rest . Llamamos al mismo tipo con Rest
  2. Aquí es donde entra la segunda condición. Aquí comprobamos si hay un tipo al final. Este es el caso de "orders/:orderID" . "orderID" y devolvemos este tipo literal.
  3. Si no queda más parámetro de ruta, regresamos nunca.

Dan Vanderkam muestra un tipo similar y más elaborado para ParseRouteParams , pero el que ves arriba también debería funcionar. Si probamos nuestro ParseRouteParams recién adaptado, obtenemos algo como esto:

 // Params is "userID" type Params = ParseRouteParams<"/api/user/:userID"> // MoreParams is "userID" | "orderID" type MoreParams = ParseRouteParams<"/api/user/:userID/orders/:orderId">

Apliquemos este nuevo tipo y veamos cómo se ve nuestro uso final de app.get .

 app.get("/api/users/:userID/orders/:orderID", function (req, res) { req.params.userID; // YES!! req.params.orderID; // Also YES!!! });

Guau. ¡Eso se parece al código JavaScript que teníamos al principio!

Tipos estáticos para comportamiento dinámico

Los tipos que acabamos de crear para una función app.get se aseguran de que excluyamos una tonelada de posibles errores:

  1. Solo podemos pasar códigos de estado numéricos adecuados a res.status()
  2. req.method es una de las cuatro cadenas posibles, y cuando usamos app.get , sabemos que solo es "GET"
  3. Podemos analizar los parámetros de ruta y asegurarnos de que no tengamos errores tipográficos dentro de nuestra devolución de llamada.

Si observamos el ejemplo del principio de este artículo, obtenemos los siguientes mensajes de error:

 app.get("/api/users/:userID", function(req, res) { if (req.method === "POST") { // ^^^^^^^^^^^^^^^^^^^^^ // This condition will always return 'false' // since the types '"GET"' and '"POST"' have no overlap. res.status(20).send({ // ^^ // Argument of type '20' is not assignable to // parameter of type 'StatusCode' message: "Welcome, user " + req.params.userId // ^^^^^^ // Property 'userId' does not exist on type // '{ userID: string; }'. Did you mean 'userID'? }); } })

¡Y todo eso antes de ejecutar nuestro código! Los servidores de estilo Express son un ejemplo perfecto de la naturaleza dinámica de JavaScript. Dependiendo del método que llame, la cadena que pase como primer argumento, muchos cambios de comportamiento dentro de la devolución de llamada. Tome otro ejemplo y todos sus tipos se ven completamente diferentes.

Pero con algunos tipos bien definidos, podemos detectar este comportamiento dinámico mientras editamos nuestro código. ¡En tiempo de compilación con tipos estáticos, no en tiempo de ejecución cuando las cosas se disparan!

Y este es el poder de TypeScript. Un sistema de tipo estático que intenta formalizar todo el comportamiento dinámico de JavaScript que todos conocemos tan bien. Si desea probar el ejemplo que acabamos de crear, diríjase al área de juegos de TypeScript y juegue con él.


Texto mecanografiado en 50 lecciones de Stefan Baumgartner En este artículo, tocamos muchos conceptos. Si desea obtener más información, consulte TypeScript en 50 lecciones, donde obtiene una introducción suave al sistema de tipos en lecciones pequeñas y fáciles de digerir. Las versiones de libros electrónicos están disponibles de inmediato, y el libro impreso será una gran referencia para su biblioteca de codificación.