Anuncio de TypeScript 3.4 RC

Hace unos días anunciamos la disponibilidad de nuestro candidato de versión (RC) de TypeScript 3.4. Nuestra esperanza es recopilar comentarios y problemas iniciales para garantizar que nuestro lanzamiento final sea fácil de recoger y usar de inmediato.


Para comenzar a usar el RC, puede obtenerlo a través de NuGet o usar npm con el siguiente comando:


npm install -g typescript@rc 

También puede obtener soporte del editor por



¡Exploremos las novedades en 3.4!




Este artículo en nuestro blog.

Compilaciones posteriores más rápidas con la bandera --incremental


Debido a que los archivos TypeScript están compilados, introduce un paso intermedio entre escribir y ejecutar su código. Uno de nuestros objetivos es minimizar el tiempo construido dado cualquier cambio en su programa. Una forma de hacerlo es ejecutando TypeScript en modo --watch . Cuando un archivo cambia en el modo --watch , TypeScript puede usar el gráfico de dependencia previamente construido de su proyecto para determinar qué archivos podrían haber sido afectados y necesitan ser revisados ​​y reemitidos. Esto puede evitar una verificación de tipo completa y una reemisión que puede ser costosa.


Pero no es realista esperar que todos los usuarios mantengan un proceso tsc --watch se ejecuta durante la noche solo para tener versiones más rápidas mañana por la mañana. ¿Qué pasa con las construcciones frías? En los últimos meses, hemos estado trabajando para ver si hay una manera de guardar la información apropiada del modo --watch en un archivo y usarla de compilación en compilación.


TypeScript 3.4 presenta una nueva --incremental llamada --incremental que le dice a TypeScript que guarde información sobre el gráfico del proyecto desde la última compilación. La próxima vez que se invoque TypeScript con --incremental , utilizará esa información para detectar la forma menos costosa de verificar y emitir cambios en su proyecto.


 // tsconfig.json { "compilerOptions": { "incremental": true, "outDir": "./lib" }, "include": ["./src"] } 

De manera predeterminada con esta configuración, cuando ejecutamos tsc , TypeScript buscará un archivo llamado .tsbuildinfo en nuestro directorio de salida ( ./lib ). Si ./lib/.tsbuildinfo no existe, se generará. Pero si lo hace, tsc intentará usar ese archivo para verificar y actualizar gradualmente nuestros archivos de salida.


Estos archivos .tsbuildinfo se pueden eliminar de forma segura y no tienen ningún impacto en nuestro código en tiempo de ejecución; se utilizan exclusivamente para hacer que las compilaciones sean más rápidas. También podemos nombrarlos como queramos y colocarlos en cualquier lugar que queramos usando el indicador --tsBuildInfoFile .


 // front-end.tsconfig.json { "compilerOptions": { "incremental": true, "tsBuildInfoFile": "./buildcache/front-end", "outDir": "./lib" }, "include": ["./src"] } 

Mientras nadie más intente escribir en el mismo archivo de caché, deberíamos poder disfrutar de compilaciones en frío incrementales más rápidas.


Proyectos compuestos


Parte de la intención con proyectos compuestos ( tsconfig.json s con composite establecido en true ) es que las referencias entre diferentes proyectos se pueden construir de forma incremental. Como tal, los proyectos compuestos siempre producirán archivos .tsbuildinfo .


outFile


Cuando se usa outFile , el nombre del archivo de información de compilación se basará en el nombre del archivo de salida. Como ejemplo, si nuestro archivo JavaScript de salida es ./output/foo.js , entonces, bajo el indicador --incremental , TypeScript generará el archivo ./output/foo.tsbuildinfo . Como se --tsBuildInfoFile anteriormente, esto se puede controlar con el indicador --tsBuildInfoFile .


El formato de archivo --incremental y el control de versiones


Si bien el archivo generado por --incremental es JSON, el archivo no está destinado a ser consumido por ninguna otra herramienta. No podemos ofrecer ninguna garantía de estabilidad para su contenido y, de hecho, nuestra política actual es que cualquier versión de TypeScript no comprenderá los archivos .tsbuildinfo generados a partir de otra versión.


Mejoras para ReadonlyArray y readonly ReadonlyArray


TypeScript 3.4 hace que sea un poco más fácil usar tipos de matriz de solo lectura.


Una nueva sintaxis para ReadonlyArray


El tipo ReadonlyArray describe las ReadonlyArray que solo se pueden leer. Cualquier variable con un identificador para un ReadonlyArray no puede agregar, eliminar o reemplazar ningún elemento de la matriz.


 function foo(arr: ReadonlyArray<string>) { arr.slice(); // okay arr.push("hello!"); // error! } 

Si bien a menudo es una buena práctica usar ReadonlyArray sobre Array con el propósito de intención, a menudo ha sido una molestia dado que las matrices tienen una sintaxis más agradable. Específicamente, el number[] es una versión abreviada de Array<number> , al igual que Date[] es una abreviatura de Array<Date> .


TypeScript 3.4 presenta una nueva sintaxis para ReadonlyArray utilizando un nuevo modificador de readonly para tipos de matriz.


 function foo(arr: readonly string[]) { arr.slice(); // okay arr.push("hello!"); // error! } 

tuplas de readonly


TypeScript 3.4 también presenta un nuevo soporte para tuplas de readonly . Podemos prefijar cualquier tipo de tupla con la palabra clave de readonly para convertirla en una tupla de readonly , al igual que ahora podemos hacer con la sintaxis abreviada de matriz. Como es de esperar, a diferencia de las tuplas ordinarias cuyas ranuras podrían escribirse, las tuplas de solo readonly solo permiten leer desde esas posiciones.


 function foo(pair: readonly [string, string]) { console.log(pair[0]); // okay pair[1] = "hello!"; // error } 

De la misma manera que las tuplas ordinarias son tipos que se extienden desde Array , una tupla con elementos de tipo T 1 , T 2 , ... T n extiende desde Array< T 1 | T 2 | ... T n > - las tuplas de readonly son tipos que se extienden desde ReadonlyArray . Entonces, una tupla de readonly con los elementos T 1 , T 2 , ... T n extiende desde ReadonlyArray< T 1 | T 2 | ... T n > .


modificadores de tipo mapeado de readonly readonly y matrices de readonly


En versiones anteriores de TypeScript, generalizamos los tipos mapeados para operar de manera diferente en los tipos de matriz. Esto significaba que un tipo mapeado como Boxify podría funcionar tanto en matrices como en tuplas.


 interface Box<T> { value: T } type Boxify<T> = { [K in keyof T]: Box<T[K]> } // { a: Box<string>, b: Box<number> } type A = Boxify<{ a: string, b: number }>; // Array<Box<number>> type B = Boxify<number[]>; // [Box<string>, Box<number>] type C = Boxify<[string, boolean]>; 

Desafortunadamente, los tipos mapeados como el tipo de utilidad Readonly eran efectivamente no Readonly los tipos de matrices y tuplas.


 // lib.d.ts type Readonly<T> = { readonly [K in keyof T]: T[K] } // How code acted *before* TypeScript 3.4 // { readonly a: string, readonly b: number } type A = Readonly<{ a: string, b: number }>; // number[] type B = Readonly<number[]>; // [string, boolean] type C = Readonly<[string, boolean]>; 

En TypeScript 3.4, el modificador de readonly en un tipo mapeado convertirá automáticamente los tipos de matriz en sus correspondientes equivalentes de readonly .


 // How code acts now *with* TypeScript 3.4 // { readonly a: string, readonly b: number } type A = Readonly<{ a: string, b: number }>; // readonly number[] type B = Readonly<number[]>; // readonly [string, boolean] type C = Readonly<[string, boolean]>; 

Del mismo modo, podría escribir un tipo de utilidad como Tipo mapeado de escritura que readonly capacidad de readonly , y que convertiría los contenedores de matriz de readonly nuevo a sus equivalentes mutables.


 type Writable<T> = { -readonly [K in keyof T]: T[K] } // { a: string, b: number } type A = Writable<{ readonly a: string; readonly b: number }>; // number[] type B = Writable<readonly number[]>; // [string, boolean] type C = Writable<readonly [string, boolean]>; 

Advertencias


A pesar de su apariencia, el modificador de tipo de solo readonly solo se puede usar para sintaxis en tipos de matriz y tipos de tupla. No es un operador de tipo general.


 let err1: readonly Set<number>; // error! let err2: readonly Array<boolean>; // error! let okay: readonly boolean[]; // works fine 

afirmaciones const


Al declarar una variable o propiedad mutable, TypeScript a menudo amplía los valores para asegurarse de que podamos asignar cosas más adelante sin escribir un tipo explícito.


 let x = "hello"; // hurray! we can assign to 'x' later on! x = "world"; 

Técnicamente, cada valor literal tiene un tipo literal. Arriba, el tipo "hello" se amplió a la string tipo antes de inferir un tipo para x .


Una vista alternativa podría ser decir que x tiene el tipo literal original "hello" y que no podemos asignar "world" más adelante de esta manera:


 let x: "hello" = "hello"; // error! x = "world"; 

En este caso, eso parece extremo, pero puede ser útil en otras situaciones. Por ejemplo, los TypeScripters a menudo crean objetos destinados a ser utilizados en uniones discriminadas.


 type Shape = | { kind: "circle", radius: number } | { kind: "square", sideLength: number } function getShapes(): readonly Shape[] { let result = [ { kind: "circle", radius: 100, }, { kind: "square", sideLength: 50, }, ]; // Some terrible error message because TypeScript inferred // 'kind' to have the type 'string' instead of // either '"circle"' or '"square"'. return result; } 

La mutabilidad es una de las mejores heurísticas de intención que TypeScript puede usar para determinar cuándo ampliar (en lugar de analizar todo nuestro programa).


Desafortunadamente, como vimos en el último ejemplo, en JavaScript las propiedades son mutables por defecto. Esto significa que el lenguaje a menudo ampliará tipos indeseablemente, requiriendo tipos explícitos en ciertos lugares.


 function getShapes(): readonly Shape[] { // This explicit annotation gives a hint // to avoid widening in the first place. let result: readonly Shape[] = [ { kind: "circle", radius: 100, }, { kind: "square", sideLength: 50, }, ]; return result; } 

Hasta cierto punto, esto está bien, pero a medida que nuestras estructuras de datos se vuelven cada vez más complejas, esto se vuelve engorroso.


Para resolver esto, TypeScript 3.4 introduce una nueva construcción para valores literales llamada aserciones const . Su sintaxis es una aserción de tipo con const en lugar del nombre de tipo (por ejemplo, 123 as const ). Cuando construimos nuevas expresiones literales con aserciones const , podemos señalar al lenguaje que


  • no se deben ampliar los tipos literales en esa expresión (por ejemplo, no pasar de "hello" a string )
  • los literales de objeto obtienen propiedades de readonly
  • los literales de matriz se convierten en tuplas de readonly

 // Type '10' let x = 10 as const; // Type 'readonly [10, 20]' let y = [10, 20] as const; // Type '{ readonly text: "hello" }' let z = { text: "hello" } as const; 

Fuera de los archivos .tsx , también se puede usar la sintaxis de aserción de paréntesis angular.


 // Type '10' let x = <const>10; // Type 'readonly [10, 20]' let y = <const>[10, 20]; // Type '{ readonly text: "hello" }' let z = <const>{ text: "hello" }; 

Esta característica a menudo significa que los tipos que de otro modo se usarían solo para insinuar la inmutabilidad del compilador a menudo se pueden omitir.


 // Works with no types referenced or declared. // We only needed a single const assertion. function getShapes() { let result = [ { kind: "circle", radius: 100, }, { kind: "square", sideLength: 50, }, ] as const; return result; } for (const shape of getShapes()) { // Narrows perfectly! if (shape.kind === "circle") { console.log("Circle radius", shape.radius); } else { console.log("Square side length", shape.sideLength); } } 

Observe que lo anterior no necesita anotaciones de tipo. La aserción const permitió a TypeScript tomar el tipo más específico de la expresión.


Advertencias


Una cosa a tener en cuenta es que las aserciones const solo pueden aplicarse inmediatamente en expresiones literales simples.


 // Error! // A 'const' assertion can only be applied to a string, number, boolean, array, or object literal. let a = (Math.random() < 0.5 ? 0 : 1) as const; // Works! let b = Math.random() < 0.5 ? 0 as const : 1 as const; 

Otra cosa a tener en cuenta es que los contextos const no convierten inmediatamente una expresión para que sea completamente inmutable.


 let arr = [1, 2, 3, 4]; let foo = { name: "foo", contents: arr, }; foo.name = "bar"; // error! foo.contents = []; // error! foo.contents.push(5); // ...works! 

globalThis para globalThis


Puede ser sorprendentemente difícil acceder o declarar valores en el ámbito global, tal vez porque estamos escribiendo nuestro código en módulos (cuyas declaraciones locales no se filtran por defecto), o porque podríamos tener una variable local que sombree el nombre de Un valor global. En diferentes entornos, hay diferentes formas de acceder a lo que efectivamente es el alcance global: global en Node, window , self o frames en el navegador, o this en ciertas ubicaciones fuera del modo estricto. Nada de esto es obvio, y a menudo deja a los usuarios sintiéndose inseguros de si están escribiendo el código correcto.


TypeScript 3.4 presenta soporte para la verificación de tipos del nuevo globalThis ECMAScript, una variable global que, bueno, se refiere al alcance global. A diferencia de las soluciones anteriores, globalThis proporciona una forma estándar de acceder al alcance global que se puede utilizar en diferentes entornos.


 // in a global file: let abc = 100; // Refers to 'abc' from above. globalThis.abc = 200; 

globalThis también es capaz de reflejar si una variable global se declaró o no const al tratarla como una propiedad de readonly cuando se accede a ella.


 const answer = 42; globalThis.answer = 333333; // error! 

Es importante tener en cuenta que TypeScript no transforma las referencias a globalThis al compilar versiones anteriores de ECMAScript. Como tal, a menos que esté apuntando a navegadores de hoja perenne (que ya admiten globalThis ), es posible que desee usar un polyfill apropiado en su lugar.


Convertir a parámetros con nombre


A veces, las listas de parámetros comienzan a ser difíciles de manejar.


 function updateOptions( hue?: number, saturation?: number, brightness?: number, positionX?: number, positionY?: number positionZ?: number) { // .... } 

En el ejemplo anterior, es demasiado fácil para una persona que llama mezclar el orden de los argumentos dados. Un patrón común de JavaScript es usar un "objeto de opciones", de modo que cada opción se nombre explícitamente y el orden nunca importe. Esto emula una característica que otros idiomas han llamado "parámetros con nombre".


 interface Options { hue?: number, saturation?: number, brightness?: number, positionX?: number, positionY?: number positionZ?: number } function updateOptions(options: Options = {}) { // .... } 

El equipo de TypeScript no solo trabaja en un compilador, también brindamos la funcionalidad que los editores usan para funciones ricas como terminaciones, definición y refactorizaciones. En TypeScript 3.4, nuestra pasante Gabriela Britto ha implementado una nueva refactorización para convertir las funciones existentes para usar este patrón de "parámetros con nombre".


Se aplica una refactorización a una función para que tome parámetros con nombre.


Si bien podemos cambiar el nombre de la función en nuestra versión 3.4 final y creemos que puede haber espacio para parte de la ergonomía, nos encantaría que pruebe la función y nos envíe sus comentarios.


Rompiendo cambios


Nivel superior this ahora está escrito


El tipo de nivel superior ahora se escribe como typeof globalThis lugar de any . Como consecuencia, puede recibir errores por acceder a valores desconocidos en this bajo noImplicitAny .


 // previously okay in noImplicitAny, now an error this.whargarbl = 10; 

Tenga en cuenta que el código compilado bajo noImplicitThis no experimentará ningún cambio aquí.


Argumentos propagados de tipo genérico


En ciertos casos, la inferencia mejorada de TypeScript 3.4 puede producir funciones que son genéricas, en lugar de las que toman y devuelven sus restricciones (generalmente {} ).


 declare function compose<T, U, V>(f: (arg: T) => U, g: (arg: U) => V): (arg: T) => V; function list<T>(x: T) { return [x]; } function box<T>(value: T) { return { value }; } let f = compose(list, box); let x = f(100) // In TypeScript 3.4, 'x.value' has the type // // number[] // // but it previously had the type // // {}[] // // So it's now an error to push in a string. x.value.push("hello"); 

Una anotación de tipo explícito en x puede eliminar el error.


Que sigue


TypeScript 3.4 es nuestra primera versión que ha tenido un plan de iteración que describe nuestros planes para esta versión, que está destinado a alinearse con nuestra hoja de ruta de 6 meses . Puede estar atento a ambos y a nuestra página de hoja de ruta de funciones para cualquier próximo trabajo.


En este momento esperamos escuchar su experiencia con el RC, ¡así que pruébelo ahora y díganos qué piensa!

Source: https://habr.com/ru/post/443996/


All Articles