TypeScript es una opción popular para programadores acostumbrados a otros lenguajes con tipado estático, como C# y Java.
El sistema de tipos de TypeScript ofrece muchos de los mismos beneficios, como una mejor finalización de código, detección temprana de errores y una comunicación más clara entre las partes de tu programa. Aunque TypeScript proporciona muchas características familiares para estos desarrolladores, vale la pena dar un paso atrás para ver cómo JavaScript (y por lo tanto TypeScript) difieren de los lenguajes POO tradicionales. Comprender estas diferencias te ayudará a escribir mejor código JavaScript y a evitar errores comunes en los que pueden caer los programadores que van directamente de C#/Java a TypeScript.
Co-aprendizaje de JavaScript
Si ya estás familiarizado con JavaScript pero eres principalmente un programador de Java o C#, esta página introductoria puede ayudar a explicar algunos de los conceptos erróneos y dificultades comunes a los que podrías ser susceptible. Algunas de las formas en que TypeScript modela los tipos son bastante diferentes de Java o C#, y es importante tenerlas en cuenta al aprender TypeScript.
Si eres un programador de Java o C# que es nuevo en JavaScript en general, te recomendamos aprender un poco de JavaScript sin tipos primero para comprender los comportamientos en tiempo de ejecución de JavaScript. Debido a que TypeScript no cambia cómo se ejecuta tu código, ¡todavía tendrás que aprender cómo funciona JavaScript para escribir código que realmente haga algo!
Es importante recordar que TypeScript usa el mismo entorno de ejecución que JavaScript, por lo que cualquier recurso sobre cómo lograr un comportamiento específico en tiempo de ejecución (convertir una cadena a un número, mostrar una alerta, escribir un archivo en disco, etc.) siempre se aplicará igualmente bien a los programas TypeScript. ¡No te limites a los recursos específicos de TypeScript!
Repensando la Clase
C# y Java son lo que podríamos llamar lenguajes de POO obligatoria. En estos lenguajes, la clase es la unidad básica de organización del código, y también el contenedor básico de todos los datos y comportamiento en tiempo de ejecución. Forzar que toda la funcionalidad y los datos se mantengan en clases puede ser un buen modelo de dominio para algunos problemas, pero no todos los dominios necesitan ser representados de esta manera.
Funciones Libres y Datos
En JavaScript, las funciones pueden vivir en cualquier lugar, y los datos pueden pasarse libremente sin estar dentro de una class o struct predefinida.
Esta flexibilidad es extremadamente poderosa.
Las funciones “libres” (aquellas no asociadas con una clase) que trabajan sobre datos sin una jerarquía POO implícita tienden a ser el modelo preferido para escribir programas en JavaScript.
Clases Estáticas
Además, ciertas construcciones de C# y Java como los singletons y las clases estáticas son innecesarias en TypeScript.
POO en TypeScript
Dicho esto, ¡todavía puedes usar clases si quieres! Algunos problemas se adaptan bien a ser resueltos por una jerarquía POO tradicional, y el soporte de TypeScript para las clases de JavaScript hará que estos modelos sean aún más poderosos. TypeScript admite muchos patrones comunes como la implementación de interfaces, la herencia y los métodos estáticos.
Cubriremos las clases más adelante en esta guía.
Repensando los Tipos
La comprensión de TypeScript de un tipo es en realidad bastante diferente de la de C# o Java. Exploremos algunas diferencias.
Sistemas de Tipos Nominales Reificados
En C# o Java, cualquier valor u objeto dado tiene un tipo exacto: null, un primitivo o un tipo de clase conocido.
Podemos llamar a métodos como value.GetType() o value.getClass() para consultar el tipo exacto en tiempo de ejecución.
La definición de este tipo residirá en una clase en algún lugar con algún nombre, y no podemos usar dos clases con formas similares en lugar de la otra a menos que haya una relación de herencia explícita o una interfaz comúnmente implementada.
Estos aspectos describen un sistema de tipos reificado y nominal. Los tipos que escribimos en el código están presentes en tiempo de ejecución, y los tipos se relacionan a través de sus declaraciones, no de sus estructuras.
Tipos como Conjuntos
En C# o Java, tiene sentido pensar en una correspondencia uno a uno entre los tipos en tiempo de ejecución y sus declaraciones en tiempo de compilación.
En TypeScript, es mejor pensar en un tipo como un conjunto de valores que comparten algo en común. Debido a que los tipos son solo conjuntos, un valor particular puede pertenecer a muchos conjuntos al mismo tiempo.
Una vez que comienzas a pensar en los tipos como conjuntos, ciertas operaciones se vuelven muy naturales.
Por ejemplo, en C#, es incómodo pasar un valor que es o bien un string o un int, porque no hay un solo tipo que represente este tipo de valor.
En TypeScript, esto se vuelve muy natural una vez que te das cuenta de que cada tipo es solo un conjunto.
¿Cómo describes un valor que pertenece al conjunto string o al conjunto number?
Simplemente pertenece a la unión de esos conjuntos: string | number.
TypeScript proporciona una serie de mecanismos para trabajar con tipos de manera teórico-conjuntista, y los encontrarás más intuitivos si piensas en los tipos como conjuntos.
Tipos Estructurales Borrados
En TypeScript, los objetos no son de un único tipo exacto. Por ejemplo, si construimos un objeto que satisface una interfaz, podemos usar ese objeto donde se espera esa interfaz aunque no hubiera una relación declarativa entre los dos.
tsTryinterfacePointlike {x : number;y : number;}interfaceNamed {name : string;}functionlogPoint (point :Pointlike ) {console .log ("x = " +point .x + ", y = " +point .y );}functionlogName (x :Named ) {console .log ("Hola, " +x .name );}constobj = {x : 0,y : 0,name : "Origin",};logPoint (obj );logName (obj );
El sistema de tipos de TypeScript es estructural, no nominal: Podemos usar obj como Pointlike porque tiene propiedades x e y que son ambos números.
Las relaciones entre tipos están determinadas por las propiedades que contienen, no si fueron declaradas con alguna relación particular.
El sistema de tipos de TypeScript tampoco es reificado: No hay nada en tiempo de ejecución que nos diga que obj es Pointlike.
De hecho, el tipo Pointlike no está presente de ninguna forma en tiempo de ejecución.
Volviendo a la idea de tipos como conjuntos, podemos pensar que obj es miembro tanto del conjunto de valores Pointlike como del conjunto de valores Named.
Consecuencias del Tipado Estructural
Los programadores de POO a menudo se sorprenden por dos aspectos particulares del tipado estructural.
Tipos Vacíos
El primero es que el tipo vacío parece desafiar las expectativas:
tsTryclassEmpty {}functionfn (arg :Empty ) {// ¿hacer algo?}// No hay error, pero esto no es un 'Empty' ?fn ({k : 10 });
TypeScript determina si la llamada a fn aquí es válida viendo si el argumento proporcionado es un Empty válido.
Lo hace examinando la estructura de { k: 10 } y class Empty { }.
Podemos ver que { k: 10 } tiene todas las propiedades que tiene Empty, porque Empty no tiene propiedades.
¡Por lo tanto, esta es una llamada válida!
Esto puede parecer sorprendente, pero en última instancia es una relación muy similar a la que se aplica en los lenguajes POO nominales. Una subclase no puede eliminar una propiedad de su clase base, porque hacerlo destruiría la relación de subtipo natural entre la clase derivada y su base. Los sistemas de tipos estructurales simplemente identifican esta relación implícitamente describiendo subtipos en términos de tener propiedades de tipos compatibles.
Tipos Idénticos
Otra fuente frecuente de sorpresa viene con tipos idénticos:
tsclass Car {drive() {// pisar el acelerador}}class Golfer {drive() {// golpear la bola lejos}}// ¿Sin error?let w: Car = new Golfer();
Nuevamente, esto no es un error porque las estructuras de estas clases son las mismas. Aunque esto pueda parecer una fuente potencial de confusión, en la práctica, las clases idénticas que no deberían estar relacionadas no son comunes.
Aprenderemos más sobre cómo se relacionan las clases entre sí en el capítulo de Clases.
Reflexión
Los programadores de POO están acostumbrados a poder consultar el tipo de cualquier valor, incluso uno genérico:
csharp// C#static void LogType<T>() {Console.WriteLine(typeof(T).Name);}
Debido a que el sistema de tipos de TypeScript se borra por completo, la información sobre, por ejemplo, la instanciación de un parámetro de tipo genérico no está disponible en tiempo de ejecución.
JavaScript tiene algunas primitivas limitadas como typeof e instanceof, pero recuerda que estos operadores todavía funcionan sobre los valores tal como existen en el código de salida sin tipos.
Por ejemplo, typeof (new Car()) será "object", no Car o "Car".
Próximos Pasos
Esto fue una breve descripción general de la sintaxis y las herramientas utilizadas en el código TypeScript cotidiano. Desde aquí, puedes:
- Leer el Manual completo de principio a fin
- Explorar los ejemplos del Playground