TypeScript, menos TypeScript

Índice
  1. Configurando un nuevo proyecto
    1. Un ejemplo sencillo
    2. Definiendo nuestros propios tipos
    3. Trabajar con genéricos
    4. Tipo de fundición
    5. Terminando

A menos que haya estado escondido debajo de una roca durante los últimos años (y seamos realistas, esconderse debajo de una roca a veces parece lo correcto), probablemente haya oído hablar de TypeScript y probablemente lo haya usado. TypeScript es un superconjunto sintáctico de JavaScript que agrega, como su nombre indica, escritura al lenguaje de secuencias de comandos favorito de la web.

TypeScript es increíblemente poderoso, pero a menudo es difícil de leer para los principiantes y conlleva la sobrecarga de necesitar un paso de compilación antes de poder ejecutarse en un navegador debido a la sintaxis adicional que no es válida para JavaScript. Para muchos proyectos esto no es un problema, pero para otros puede obstaculizar la realización del trabajo. Afortunadamente, el equipo de TypeScript ha habilitado una forma de verificar el tipo de JavaScript básico usando JSDoc.

Configurando un nuevo proyecto

Para poner TypeScript en funcionamiento en un nuevo proyecto, necesitará NodeJS y npm. Comenzamos a crear un nuevo proyecto y ejecutando npm init. Para los propósitos de este artículo, usaremos VShttps://code.visualstudio.comCode como nuestro editor de código. Una vez que todo esté configurado, necesitaremos instalar TypeScript:

npm i -D typescript

Una vez realizada la instalación, debemos decirle a TypeScript qué hacer con nuestro código, así que creamos un nuevo archivo llamado tsconfig.jsony agregamos esto:

{  "compilerOptions": {    "target": "esnext",    "module": "esnext",    "moduleResolution": "node",    "lib": ["es2017", "dom"],    "allowJs": true,    "checkJs": true,    "noEmit": true,    "strict": false,    "noImplicitThis": true,    "alwaysStrict": true,    "esModuleInterop": true  },  "include": [ "script", "test" ],  "exclude": [ "node_modules" ]}

Para nuestros propósitos, las líneas importantes de este archivo de configuración son las opciones allowJsy checkJsambas configuradas en true. Estos le dicen a TypeScript que queremos que evalúe nuestro código JavaScript. También le hemos dicho a TypeScript que verifique todos los archivos dentro de un /scriptdirectorio, así que creemos eso y un nuevo archivo llamado index.js.

Un ejemplo sencillo

Dentro de nuestro archivo JavaScript recién creado, creemos una función de suma simple que toma dos parámetros y los suma:

function add(x, y) {  return x + y;}

Bastante simple, ¿verdad? add(4, 2)devolverá 6, pero debido a que JavaScript se escribe dinámicamente, también puedes llamar a add con una cadena y un número y obtener algunos resultados potencialmente inesperados:

add('4', 2); // returns '42'

Eso no es lo ideal. Afortunadamente, podemos agregar algunas anotaciones JSDoc a nuestra función para decirles a los usuarios cómo esperamos que funcione:

/** * Add two numbers together * @param {number} x * @param {number} y * @return {number} */function add(x, y) {  return x + y;}

No hemos cambiado nada sobre nuestro código; simplemente agregamos un comentario para decirles a los usuarios cómo se debe usar la función y qué valor se debe esperar que devuelva. Hemos hecho esto utilizando JSDoc @paramy @returnanotaciones con tipos establecidos entre llaves ( {}).

Intentar ejecutar nuestro fragmento incorrecto anterior genera un error en VS Code:

En el ejemplo anterior, TypeScript lee nuestro comentario y lo verifica por nosotros. En TypeScript real, nuestra función ahora es equivalente a escribir:

/** * Add two numbers together */function add(x: number, y: number): number {  return x + y;}

Al igual que usamos el numbertipo, tenemos acceso a docenas de tipos integrados con JSDoc, incluidos string, object, Array y muchos otros, como HTMLElementy MutationRecordmás.

Un beneficio adicional de usar anotaciones JSDoc sobre la sintaxis patentada de TypeScript es que brinda a los desarrolladores la oportunidad de proporcionar metadatos adicionales en torno a argumentos o definiciones de tipos al proporcionarlos en línea (con suerte, fomentando hábitos positivos de autodocumentar nuestro código).

También podemos decirle a TypeScript que las instancias de ciertos objetos pueden tener expectativas. A WeakMap, por ejemplo, es un objeto JavaScript integrado que crea una asignación entre cualquier objeto y cualquier otro dato. Este segundo dato puede ser cualquier cosa de forma predeterminada, pero si queremos que nuestra WeakMapinstancia solo tome una cadena como valor, podemos decirle a TypeScript lo que queremos:

/** @type {WeakMapobject, string} */const metadata = new WeakMap();
const object = {};const otherObject = {};
metadata.set(object, 42);metadata.set(otherObject, 'Hello world');

Esto genera un error cuando intentamos configurar nuestros datos 42porque no son una cadena.

Definiendo nuestros propios tipos

Al igual que TypeScript, JSDoc nos permite definir y trabajar con nuestros propios tipos. Creemos un nuevo tipo llamado Personque tiene namey agepropiedades hobby. Así es como se ve en TypeScript:

interface Person {  name: string;  age: number;  hobby?: string;}

En JSDoc, nuestro tipo sería el siguiente:

/** * @typedef Person * @property {string} name - The person's name * @property {number} age - The person's age * @property {string} [hobby] - An optional hobby */

Podemos usar la @typedefetiqueta para definir nuestro tipo name. Definimos una interfaz de llamada Personcon las propiedades requeridas name(una cadena)) y age(un número), más una tercera propiedad opcional llamada hobby(una cadena). Para definir estas propiedades, usamos @property(o la @propclave abreviada) dentro de nuestro comentario.

Cuando elegimos aplicar el Persontipo a un nuevo objeto usando el @typecomentario, obtenemos verificación de tipo y autocompletar al escribir nuestro código. No solo eso, también se nos avisará cuando nuestro objeto no cumpla con el contrato que hemos definido en nuestro archivo:

Ahora, completar el objeto borrará el error:

A veces, sin embargo, no queremos un objeto completo para un tipo. Por ejemplo, es posible que queramos proporcionar un conjunto limitado de opciones posibles. En este caso, podemos aprovechar algo llamado tipo de unión:

/** * @typedef {'cat'|'dog'|'fish'} Pet */
/** * @typedef Person * @property {string} name - The person's name * @property {number} age - The person's age * @property {string} [hobby] - An optional hobby * @property {Pet} [pet] - The person's pet */

En este ejemplo, hemos definido un tipo de unión llamado Petque puede ser cualquiera de las opciones posibles de 'cat', 'dog'o 'fish'. Cualquier otro animal en nuestra área no está permitido como mascota, por lo que si calebarriba intentamos adoptar uno 'kangaroo'en su hogar, obtendríamos un error:

/** @type {Person} */const caleb = {  name: 'Caleb Williams',  age: 33,  hobby: 'Running',  pet: 'kangaroo'};

Esta misma técnica se puede utilizar para mezclar varios tipos en una función:

/** * @typedef {'lizard'|'bird'|'spider'} ExoticPet */
/** * @typedef Person * @property {string} name - The person's name * @property {number} age - The person's age * @property {string} [hobby] - An optional hobby * @property {Pet|ExoticPet} [pet] - The person's pet */

Ahora nuestro tipo de persona puede tener a Peto ExoticPet.

Trabajar con genéricos

Puede haber ocasiones en las que no queramos tipos estrictos y rápidos, sino un poco más de flexibilidad mientras seguimos escribiendo código consistente y fuertemente tipado. Introduzca tipos genéricos. El ejemplo clásico de una función genérica es la función de identidad, que toma un argumento y se lo devuelve al usuario. En TypeScript, se ve así:

function identityT(target: T): T {  return target;}

Aquí, estamos definiendo un nuevo tipo genérico ( T) y le decimos a la computadora y a nuestros usuarios que la función devolverá un valor que comparte un tipo con cualquier argumento target. De esta manera, aún podemos pasar un número, una cadena o un HTMLElementy tener la seguridad de que el valor devuelto también es del mismo tipo.

Lo mismo es posible usando la notación JSDoc usando la @templateanotación:

/** * @template T * @param {T} target * @return {T} */function identity(target) {  return x;}

Los genéricos son un tema complejo, pero para obtener documentación más detallada sobre cómo utilizarlos en JSDoc, incluidos ejemplos, puede leer la página del Compilador de cierre de Google sobre el tema.

Tipo de fundición

Si bien la escritura segura suele ser muy útil, es posible que descubra que las expectativas integradas de TypeScript no funcionan del todo para su caso de uso. En ese tipo de casos, es posible que necesitemos convertir un objeto a un nuevo tipo. Un caso en el que esto podría ser necesario es cuando se trabaja con detectores de eventos.

En TypeScript, todos los detectores de eventos toman una función como devolución de llamada donde el primer argumento es un objeto de tipo Event, que tiene una propiedad, destino, que es un archivo EventTarget. Este es el tipo correcto según el estándar DOM, pero muchas veces la información que queremos obtener del objetivo del evento no existe EventTarget, como la propiedad de valor que existe en HTMLInputElement.prototype. Eso hace que el siguiente código no sea válido:

document.querySelector('input').addEventListener(event = {  console.log(event.target.value);};

TypeScript se quejará de que la propiedad valueno existe EventTargetaunque nosotros, como desarrolladores, sabemos muy bien que inputtiene un archivo value.

Para que podamos decirle a TypeScript que sabemos que event.targetserá un HTMLInputElement, debemos convertir el tipo del objeto:

document.getElementById('input').addEventListener('input', event = {  console.log(/** @type {HTMLInputElement} */(event.target).value);});

Colocarlo event.targetentre paréntesis lo diferenciará de la llamada a value. Agregar el tipo antes del paréntesis le indicará a TypeScript que queremos decir que event.targetes algo diferente de lo que normalmente espera.

Y si un objeto en particular está siendo problemático, siempre podemos decirle a TypeScript que un objeto debe @type {any}ignorar los mensajes de error, aunque esto generalmente se considera una mala práctica a pesar de ser útil en caso de apuro.

Terminando

TypeScript es una herramienta increíblemente poderosa que muchos desarrolladores utilizan para optimizar su flujo de trabajo en torno a estándares de código consistentes. Si bien la mayoría de las aplicaciones utilizarán el compilador integrado, algunos proyectos pueden decidir que la sintaxis adicional que proporciona TypeScript se interpone en su camino. O tal vez simplemente se sienten más cómodos apegándose a los estándares en lugar de estar atados a una sintaxis ampliada. En esos casos, los desarrolladores aún pueden obtener los beneficios de utilizar el sistema de tipos de TypeScript incluso mientras escriben JavaScript básico.

SUSCRÍBETE A NUESTRO BOLETÍN 
No te pierdas de nuestro contenido ni de ninguna de nuestras guías para que puedas avanzar en los juegos que más te gustan.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir

Este sitio web utiliza cookies para mejorar tu experiencia mientras navegas por él. Este sitio web utiliza cookies para mejorar tu experiencia de usuario. Al continuar navegando, aceptas su uso. Mas informacion