TypeScript para programadores de Java / C#

Lo que debes saber...

TypeScript es una opción popular para los programadores acostumbrados a otros lenguajes con escritura estática, como C # y Java.

El sistema de tipos de TypeScript ofrece muchos de los mismos beneficios, como una mejor finalización del código, una detección más temprana de errores y una comunicación más clara entre las partes de su programa. Si bien 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 OOP tradicionales. Comprender estas diferencias lo ayudará a escribir mejor código JavaScript y evitará los errores comunes en los que pueden caer los programadores que pasan directamente de C # / Java a TypeScript.

TypeScript amplía la sintaxis de JS y agrega nuevas características orientadas a objetos como interfaces, clases, módulos y anotaciones, lo que lo hace más claro en la estructura del código y la división de módulos, y el código legible. Así que algunas personas dicen "TypeScript hace que JavaScript vuelva a ser Java".

La característica fundamental de TypeScript es que compila en Javascript nativo, por lo que se puede usar en todo proyecto donde se esté usando Javascript. Dicho con otras palabras, cuando se usa TypeScript en algún momento se realiza su compilación, convirtiendo su código a Javascript común. El navegador, o cualquier otra plataforma donde se ejecuta Javascript, nunca llegará a enterarse que el código original estaba escrito en TypeScript, porque lo único que llegará a ejecutar es el Javascript resultante de la compilación.

En resumen, TypeScript es lo que se conoce como un "superset" de Javascript, aportando herramientas avanzadas para la programación que traen grandes beneficios a los proyectos.

¿Qué necesitas para usar TypeScript

Básicamente necesitamos descargar dos programas. El primero es NodeJS, no porque necesitemos desarrollar con Node, sino porque el compilador de TypeScript está desarrollado en NodeJS. Desde el sitio de NodeJS encontramos las opciones para la instalación en nuestro sistema operativo, por medio de un típico instalador con su asistente (siguiente, siguiente…) Más información en el Manual de NodeJS. Luego necesitarás el TSC (Command-line TypeScript Compiler), la herramienta que nos permite compilar un archivo TypeScript a Javascript nativo.

npm install -g typescript

Este es un ejemplo de un código de TypeScript:

var a:number = 9;
a += 4;
                            
function mostrar(b:string) :void{
    console.log(b);
}
mostrar('hola');

Para compilarlo usarás el mencionado TSC y lanzarás un sencillo comando que convertirá el código a Javascript nativo.

tsc ejemplo.ts

Coaprendizaje de JavaScript

Si ya está familiarizado con JavaScript, pero es principalmente un programador de Java o C #, esta página de introducción puede ayudarlo a explicar algunos de los errores y errores comunes a los que podría ser susceptible. Algunas de las formas en que los tipos de modelos de TypeScript son bastante diferentes de Java o C #, y es importante tenerlas en cuenta al aprender TypeScript.

Si es un programador de Java o C # que es nuevo en JavaScript en general, le recomendamos que primero aprenda un poco de JavaScript sin tipos para comprender los comportamientos en tiempo de ejecución de JavaScript. Debido a que TypeScript no cambia la forma en que se ejecuta su código , aún tendrá que aprender cómo funciona JavaScript para escribir código que realmente haga algo.

Es importante recordar que TypeScript usa el mismo tiempo de ejecución que JavaScript, por lo que cualquier recurso sobre cómo lograr un comportamiento de ejecución específico (convertir una cadena en un número, mostrar una alerta, escribir un archivo en el disco, etc.) siempre se aplicará igualmente bien a Programas TypeScript.

¡No se limite a recursos específicos de TypeScript!

Algunos ejemplos de diferencias entre Typescript y JavaScript

Tipos básicos

index.js

const foo = 'bar'
const bar = 1
const baz = true
const qux = ['bar', 'baz', 'qux']

index.ts

const foo: string = 'bar'
const bar: number = 1
const baz: boolean = true
const qux: string[] = ['bar', 'baz', 'qux']

Null

index.js

let u = undefined
let n = null
                        
let a = 'bar'
a = null
                        
let b = 'bar'
b = null
                        
if (b != null) {
    const l = b.length
    console.log(l)
}

index.ts

let n: null = null
                        
let a: string = 'bar'
// no compila: a = null
let b: string | null = 'bar'
// compila: b = null
                        
if (b != null) {
    const l = b.length
    console.log(l)
}

Funciones

index.js

function suma(a, b = 1) {
    return a + b
}
                        
const res = suma(2).toFixed(4)
console.log(res)

index.ts

function suma(a: number, b: number = 1): number {
    return a + b
}
                      
const resultado = suma(2).toFixed(4)
console.log(resultado)

Repensar la clase

C # y Java son lo que podríamos llamar lenguajes OOP obligatorios . 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 comportamientos en tiempo de ejecución. Obligando a toda la funcionalidad y los datos que se realizará en las clases puede ser un buen modelo de dominio para algunos problemas, pero no todos los dominios necesita ser representado de esta manera.

Funciones y datos gratuitos

En JavaScript, las funciones pueden vivir en cualquier lugar y los datos se pueden transmitir libremente sin estar dentro de un archivo classo struct. Esta flexibilidad es extremadamente poderosa. Las funciones "libres" (las que no están asociadas con una clase) que trabajan sobre datos sin una jerarquía OOP 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 singletons y clases estáticas, son innecesarias en TypeScript.

Más sobre las clases

OOP en TypeScript

Dicho esto, ¡aún puedes usar las clases si quieres! Algunos problemas son adecuados para ser resueltos por una jerarquía OOP tradicional, y el soporte de TypeScript para clases de JavaScript hará que estos modelos sean aún más poderosos. TypeScript admite muchos patrones comunes, como la implementación de interfaces, herencia y métodos estáticos.

Repensar los tipos

La comprensión de TypeScript de un tipo es en realidad bastante diferente a la de C # o Java. Exploremos algunas diferencias.

Sistemas de tipo nominal reificado

En C # o Java, cualquier valor u objeto dado tiene un tipo exacto, ya sea nullun tipo de clase primitivo o 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.

Tipos como conjuntos

En C # o Java, es significativo pensar en una correspondencia uno a uno entre los tipos de 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 empiezas a pensar en los tipos como conjuntos, ciertas operaciones se vuelven muy naturales. Por ejemplo, en C #, es incómodo pasar un valor que sea un string o 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 describe un valor que pertenece al string conjunto o al number conjunto? Simplemente pertenece a la unión de los conjuntos: string |number.

TypeScript proporciona una serie de mecanismos para trabajar con tipos de una manera teórica de conjuntos, y los encontrará más intuitivos si piensa en los tipos como conjuntos.

Tipos estructurales borrados

En TypeScript, los objetos no son de un solo tipo exacto. Por ejemplo, si construimos un objeto que satisface una interfaz, podemos usar ese objeto donde se espera esa interfaz aunque no haya una relación declarativa entre los dos.

interface Pointlike {
    x: number;
    y: number;
}
interface Named {
    name: string;
}
                       
function logPoint(point: Pointlike) {
    console.log("x = " + point.x + ", y = " + point.y);
}
                       
function logName(x: Named) {
    console.log("Hello, " + x.name);
}
                       
const obj = {
    x: 0,
    y: 0,
    name: "Origin",
};
                       
logPoint(obj);
logName(obj);

El sistema de tipos de TypeScript es estructural , no nominal: podemos usarlo obj como un Pointlike porque tiene x y y propiedades que son ambos números. Las relaciones entre los tipos están determinadas por las propiedades que contienen, no por si se declararon con alguna relación en particular.

Sistema de tipos de transcripción también está no cosificado : No hay nada en tiempo de ejecución que nos dicen que obj es Pointlike. De hecho, el Pointlike tipo no está presente de ninguna forma en tiempo de ejecución.

Volviendo a la idea de tipos como conjuntos , podemos pensar en ellos obj como miembros tanto del Pointlike conjunto de valores como del Named conjunto de valores.

Consecuencias de la tipificación estructural

Los programadores de programación orientada a objetos a menudo se sorprenden por dos aspectos particulares de la tipificación estructural.

Tipos vacíos

La primera es que el tipo vacío parece desafiar las expectativas:

class Empty {}
 
function fn(arg: Empty) {
    // do something?
}
                         
// No error, but this isn't an 'Empty' ?
    fn({ k: 10 });

TypeScript determina si la llamada a fn aquí es válida al ver si el argumento proporcionado es válido Empty. Lo hace examinando la estructura de {k:10} y class Empty { }. Podemos ver que {k:10} tiene todas las propiedades que Empty tiene, 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 de programación orientada a objetos 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 implícitamente esta relación al describir subtipos en términos de tener propiedades de tipos compatibles.

Tipos idénticos

Otra fuente frecuente de sorpresa viene con tipos idénticos:

class Car {
    drive() {
        // hit the gas
    }
}
class Golfer {
    drive() {
        // hit the ball far
    }
}
// No error?
let w: Car = new Golfer();

Nuevamente, esto no es un error porque las estructuras de estas clases son las mismas. Si bien esto puede parecer una fuente potencial de confusión, en la práctica, las clases idénticas que no deberían estar relacionadas no son comunes.

Tipo especial any

TypeScript también tiene un tipo especial any, que puede usar siempre que no desee que un valor en particular cause errores de verificación de tipo.

Cuando un valor es de tipo any, puede acceder a cualquier propiedad del mismo (que a su vez será de tipo any), llamarlo como una función, asignarlo a (o desde) un valor de cualquier tipo, o prácticamente cualquier otra cosa que sea sintácticamente legal:

let obj: any = { x: 0 };
// None of the following lines of code will throw compiler errors.
// Using `any` disables all further type checking, and it is assumed 
// you know the environment better than TypeScript.
obj.foo();
obj();
obj.bar = 100;
obj = "hello";
const n: number = obj;

El anytipo es útil cuando no desea escribir un tipo largo solo para convencer a TypeScript de que una línea de código en particular está bien.

Si deseas aprender más, puedes ver el siguiente video:


Curso Completo Adicional

¡Pon a prueba los conocimientos adquiridos!

Resuelve el siguiente Cuestionario

Autores: Grupo 07 de TPI115 - UES