TypeScript efectivo: 62 formas de mejorar su código

imagen¡Hola habitantes! El libro de Dan Vanderkam será muy útil para aquellos que ya tienen experiencia con JavaScript y TypeScript. El propósito de este libro no es educar a los lectores para que utilicen las herramientas, sino ayudarlos a mejorar su profesionalismo. Después de leerlo, comprenderá mejor cómo funcionan los componentes de TypeScript, evitará muchas trampas y obstáculos y desarrollará sus habilidades. Si bien la guía de referencia le mostrará cinco formas diferentes de usar el lenguaje para realizar la misma tarea, un libro "eficaz" le explicará cuál es mejor y por qué.



Estructura del libro



El libro es una colección de ensayos cortos (reglas). Las bases se agrupan en apartados temáticos (capítulos), a los que se puede acceder de forma autónoma, según la cuestión de interés.



Cada título de regla contiene una sugerencia, así que consulte la tabla de contenido. Si, por ejemplo, está escribiendo documentación y tiene dudas sobre si debe escribir información de tipo, consulte la tabla de contenido y la regla 30 (“No repita la información de tipo en la documentación”).

Casi todas las conclusiones del libro se demuestran mediante ejemplos de código. Creo que usted, como yo, tiende a leer libros técnicos mirando ejemplos y solo pasando por la parte del texto. Por supuesto, espero que lea las explicaciones detenidamente, pero he cubierto los puntos principales en los ejemplos.



Después de leer cada consejo, puede comprender exactamente cómo y por qué lo ayudará a usar TypeScript de manera más efectiva. También comprenderá si resulta inutilizable de alguna manera. Recuerdo un ejemplo dado por Scott Myers, autor de Effective C ++: los desarrolladores de software de misiles podrían haber descuidado los consejos sobre la prevención de fugas de recursos porque sus programas fueron destruidos cuando el misil alcanzó el objetivo. No tengo conocimiento de la existencia de cohetes con un sistema de control escrito en JavaScript, pero dicho software está disponible en el telescopio James Webb. Así que ten cuidado.



Cada regla termina con un bloque "Debe recordar". Al echarle un vistazo rápido, puede hacerse una idea general del material y resaltar lo principal. Pero recomiendo leer toda la regla.



Un experto. REGLA 4. Acostúmbrese a la escritura estructural



JavaScript se escribe sin querer: si pasa un valor con las propiedades correctas a una función, no le importa cómo obtuvo ese valor. Ella simplemente lo usa. TypeScript modela este comportamiento, que a veces conduce a resultados inesperados, ya que la comprensión de los tipos por parte del validador puede ser más amplia que la suya. Desarrollar la habilidad de escribir estructurado le permitirá sentir mejor dónde realmente hay errores y escribir código más confiable.



Por ejemplo, está trabajando con una biblioteca de física y tiene un tipo de vector 2D:



interface Vector2D {
   x: number;
   y: number;
}


Escribe una función para calcular su longitud:



function calculateLength(v: Vector2D) {
   return Math.sqrt(v.x * v.x + v.y * v.y);
}


e ingrese la definición del vector nombrado:



interface NamedVector {
   name: string;
   x: number;
   y: number;
}


La función calculateLength funcionará con NamedVector ya que contiene las propiedades xey, que son números. TypeScript entiende esto:



const v: NamedVector = { x: 3, y: 4, name: 'Zee' };
calculateLength(v); // ok,   5.


Lo interesante es que no declaró la relación entre Vector2D y NamedVector. Tampoco tuvo que escribir una ejecución alternativa de calculateLength para NamedVector. El sistema de tipos TypeScript simula el comportamiento en tiempo de ejecución de JavaScript (regla 1), lo que permitió que NamedVector llamara a calculateLength en función de que su estructura fuera comparable a Vector2D. De ahí la expresión "tipificación estructural".



Pero también puede ocasionar problemas. Digamos que agrega un tipo de vector 3D:



interface Vector3D {
   x: number;
   y: number;
   z: number;
}


y escribe una función para normalizar vectores (haz que su longitud sea igual a 1):



function normalize(v: Vector3D) {
   const length = calculateLength(v);
   return {
      x: v.x / length,
      y: v.y / length,
      z: v.z / length,
   };
}


Si llama a esta función, lo más probable es que obtenga más de una longitud:



> normalize({x: 3, y: 4, z: 5})
{ x: 0.6, y: 0.8, z: 1 }


¿Qué salió mal y por qué TypeScript no informó un error?



El error es que calculateLength funciona con vectores 2D, mientras que normalize funciona con 3D. Por lo tanto, el componente z se ignora durante la normalización.



Puede parecer extraño que el verificador de tipos no haya captado esto. ¿Por qué se permite llamar a calculateLength en un vector 3D, aunque su tipo funciona con vectores 2D?

Lo que funcionó bien con named ha fracasado aquí. Llamar a calculateLength en el objeto {x, y, z} no arrojará un error. Por lo tanto, el módulo de verificación de tipos no se queja, lo que finalmente conduce a la aparición de un error. Si desea que se detecte el error en este caso, consulte la regla 37.



Al escribir funciones, es fácil imaginar que serán llamadas por las propiedades que declaraste, y nada más. Esto se denomina tipo "sellado" o "exacto" y no se puede aplicar en el sistema de tipos TypeScript. Nos guste o no, los tipos están abiertos aquí.



A veces esto lleva a sorpresas:



function calculateLengthL1(v: Vector3D) {
   let length = 0;
   for (const axis of Object.keys(v)) {
      const coord = v[axis];
                       // ~~~~~~~     "any",  
                       //  "string"    
                       //    "Vector3D"
      length += Math.abs(coord);
   }
   return length;
}


¿Por qué es esto un error? Dado que el eje es una de las claves v de Vector3D, debe ser x, y o z. Y de acuerdo con la declaración original de Vector3D, todos son números. Por lo tanto, ¿no debería el tipo de coord también ser número?



Este no es un error falso. Sabemos que Vector3D está bien definido y no tiene otras propiedades. Aunque pudo:



const vec3D = {x: 3, y: 4, z: 1, address: '123 Broadway'};
calculateLengthL1(vec3D); // ok,  NaN


Dado que v probablemente podría tener cualquier propiedad, el eje es de tipo cadena. No hay ninguna razón para que TypeScript piense en v [eje] como solo un número. Al iterar sobre objetos, puede resultar difícil lograr una escritura correcta. Volveremos a este tema en la Regla 54, pero por ahora no usamos bucles:



function calculateLengthL1(v: Vector3D) {
   return Math.abs(v.x) + Math.abs(v.y) + Math.abs(v.z);
}


La tipificación estructural también puede causar sorpresas en las clases que se comparan para posibles asignaciones de propiedades:



class C {
   foo: string;
   constructor(foo: string) {
       this.foo = foo;
   }
}

const c = new C('instance of C');
const d: C = { foo: 'object literal' }; // ok!


¿Por qué se puede asignar d a C? Tiene una propiedad foo que es una cadena. También tiene un constructor (de Object.prototype) que se puede llamar con un argumento (aunque normalmente se llama sin él). Entonces las estructuras son las mismas. Esto puede llevar a sorpresas si tiene lógica en el constructor de C y escribe una función para invocarla. Esta es una diferencia significativa con los lenguajes como C ++ o Java, donde las declaraciones de un parámetro de tipo C aseguran que pertenece a C o una subclase de él.



La mecanografía estructural ayuda mucho al escribir pruebas. Digamos que tiene una función que ejecuta una consulta de base de datos y procesa el resultado.



interface Author {
   first: string;
   last: string;
}
function getAuthors(database: PostgresDB): Author[] {
   const authorRows = database.runQuery(`SELECT FIRST, LAST FROM
                                 AUTHORS`);
   return authorRows.map(row => ({first: row[0], last: row[1]}));
}


Para probarlo, puede crear un simulacro de PostgresDB. Sin embargo, una mejor solución sería utilizar la escritura estructurada y definir una interfaz más estrecha:



interface DB {
   runQuery: (sql: string) => any[];
}
function getAuthors(database: DB): Author[] {
   const authorRows = database.runQuery(`SELECT FIRST, LAST FROM
                                 AUTHORS`);
   return authorRows.map(row => ({first: row[0], last: row[1]}));
}


Aún puede pasar postgresDB a la función getAuthors en la salida, ya que tiene un método runQuery. La tipificación estructural no obliga a PostgresDB a informar que está ejecutando una base de datos. TypeScript lo resolverá por ti.



Al escribir pruebas, también puede aprobar un objeto más simple:



test('getAuthors', () => {
   const authors = getAuthors({
      runQuery(sql: string) {
         return [['Toni', 'Morrison'], ['Maya', 'Angelou']];
      }
   });
   expect(authors).toEqual([
      {first: 'Toni', last: 'Morrison'},
      {first: 'Maya', last: 'Angelou'}
   ]);
});


TypeScript determinará que la base de datos de prueba se ajusta a la interfaz. Al mismo tiempo, sus pruebas no necesitan ninguna información sobre la base de datos de salida: no se requieren bibliotecas simuladas. Al introducir la abstracción (DB), liberamos la lógica de los detalles de ejecución (PostgresDB).



Otra ventaja de la tipificación estructural es que puede romper claramente las dependencias entre bibliotecas. Para obtener más información sobre este tema, consulte la regla 51.



RECUERDE



JavaScript usa el tipo pato y TypeScript lo modela usando el tipo estructurado. Como resultado, los valores asignados a sus interfaces pueden tener propiedades no especificadas en los tipos declarados. Los tipos en TypeScript no están sellados.



Tenga en cuenta que las clases también obedecen las reglas de escritura estructural. Por lo tanto, puede terminar con una muestra de clase diferente a la esperada.



Utilice la escritura estructurada para facilitar la prueba de elementos.


REGLA 5. Restringir el uso de tipos de



El sistema de tipos en TypeScript es gradual y selectivo. La gradualidad se manifiesta en la capacidad de agregar tipos al código paso a paso, y la selectividad en la capacidad de deshabilitar el módulo de verificación de tipos cuando lo necesite. La clave para controlar en este caso es cualquier tipo:



   let age: number;
   age = '12';
// ~~~  '"12"'       'number'.
   age = '12' as any; // ok


El módulo de derechos, que indica un error, pero se puede evitar simplemente agregando como cualquiera. Cuando comienza a trabajar con TypeScript, se vuelve tentador utilizar cualquier tipo o afirmación si no comprende el error, no confía en el validador o no quiere perder tiempo explicando los tipos. Pero recuerde que cualquiera niega muchos de los beneficios de TypeScript, a saber:



Disminuye la seguridad del código



En el ejemplo anterior, según el tipo declarado, la edad es un número. Pero cualquiera permitió que se le asignara una cadena. El validador asumirá que este es un número (que es lo que declaró), lo que generará confusión.



age += 1; // ok.    age "121".


Le permite violar condiciones



Al crear una función, establece una condición que, habiendo recibido un cierto tipo de datos de una llamada, producirá el tipo correspondiente en la salida, que se viola así:



function calculateAge(birthDate: Date): number {
   // ...
}

let birthDate: any = '1990-01-19';
calculateAge(birthDate); // ok


El parámetro birthDate debe ser Date, no string. El tipo any permitió violar la condición relacionada con calculateAge. Esto puede ser especialmente problemático porque JavaScript tiende a realizar conversiones de tipo implícitas. Debido a esto, la cadena fallará en algunos casos donde se supone que lo haga el número, pero inevitablemente fallará en otros lugares.



Elimina la compatibilidad con un servicio de lenguaje



Cuando a un carácter se le asigna un tipo, los servicios de lenguaje TypeScript pueden proporcionar documentación contextual y de sustitución automática adecuada (Figura 1.3).



imagen


Sin embargo, al asignar el tipo any a los caracteres, debe hacerlo todo usted mismo (Figura 1.4).



imagen


Y renombrar también. Si tiene un tipo de Persona y funciones para formatear el nombre:



interface Person {
   first: string;
   last: string;
}

const formatName = (p: Person) => `${p.first} ${p.last}`;
const formatNameAny = (p: any) => `${p.first} ${p.last}`;


luego puede resaltar primero en el editor y seleccionar el símbolo de cambio de nombre para renombrarlo a firstName (Figura 1.5 y Figura 1.6).



Esto cambiará la función formatName, pero no en el caso de ninguno:



interface Person {
   first: string;
   last: string;
}

const formatName = (p: Person) => `${p.firstName} ${p.last}`;
const formatNameAny = (p: any) => `${p.first} ${p.last}`;




imagen




»Más detalles sobre el libro se pueden encontrar en el sitio web de la editorial

» Tabla de contenido

» Extracto



para los habitantes un 25% de descuento en el cupón - TypeScript



Al pagar la versión impresa del libro, se envía un libro electrónico al correo electrónico.



All Articles