宣布TypeScript 3.4 RC

几天前,我们宣布了TypeScript 3.4的候选发布(RC)的可用性。 我们希望收集反馈和早期问题,以确保最终版本易于获取和使用。


要开始使用RC,可以通过NuGet通过以下命令使用npm:


npm install -g typescript@rc 

您还可以通过以下方式获得编辑器支持



让我们探索3.4的新功能!




本文在我们的博客中。

使用--incremental标志更快地进行后续构建


由于TypeScript文件是已编译的,因此它引入了编写和运行代码之间的中间步骤。 我们的目标之一是在程序有任何更改的情况下最大程度地减少构建时间。 一种方法是在--watch模式下运行TypeScript。 当文件在--watch模式下更改时,TypeScript能够使用项目的先前构造的依赖关系图来确定哪些文件可能已受到影响,并且需要重新检查和重新发送。 这样可以避免进行全面的类型检查和重新发射,而这可能会造成很大的成本。


但是,期望所有用户保持通宵运行tsc --watch进程只是为了明天早上有更快的构建是不现实的。 那冷建物呢? 在过去的几个月中,我们一直在研究是否存在一种方法,可以将--watch模式下的适当信息保存到文件中,并在各个版本之间使用它。


TypeScript 3.4引入了一个称为--incremental的新标志,该标志告诉TypeScript保存上一次编译中有关项目图的信息。 下次使用--incremental调用TypeScript时,它将使用该信息来检测最廉价的类型检查和对项目进行更改的方式。


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

默认情况下,使用这些设置,当我们运行tsc ,TypeScript将在输出目录( ./lib )中查找名为.tsbuildinfo的文件。 如果./lib/.tsbuildinfo不存在,它将生成。 但是,如果这样做, tsc将尝试使用该文件进行增量类型检查并更新我们的输出文件。


这些.tsbuildinfo文件可以安全删除,并且在运行时不会对我们的代码产生任何影响-它们纯粹是用来加快编译速度。 我们还可以为它们命名所需的任何名称,并使用--tsBuildInfoFile标志将它们放置在--tsBuildInfoFile任何位置。


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

只要没有其他人尝试写入相同的缓存文件,我们就应该能够享受到更快的增量冷构建。


复合项目


组合项目( tsconfig.jsoncomposite设置为true )的部分意图是,可以增量构建不同项目之间的引用。 因此,复合项目将始终生成.tsbuildinfo文件。


outFile


使用outFile ,构建信息文件的名称将基于输出文件的名称。 例如,如果我们的输出JavaScript文件是./output/foo.js ,则在--incremental标志下,TypeScript将生成文件./output/foo.tsbuildinfo 。 如上所述,可以使用--tsBuildInfoFile标志进行控制。


- --incremental文件格式和版本控制


虽然--incremental生成的文件是JSON,但并不意味着该文件会被其他任何工具使用。 我们不能为其内容提供任何保证,事实上,我们当前的政策是TypeScript的任何一个版本都不会理解从另一个版本生成的.tsbuildinfo文件。


ReadonlyArrayreadonly元组的改进


TypeScript 3.4使使用类似数组的只读类型更加容易。


ReadonlyArray的新语法


ReadonlyArray类型描述只能读取的Array 。 具有ReadonlyArray句柄的任何变量都不能添加,删除或替换数组的任何元素。


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

虽然出于意图目的在Array上使用ReadonlyArray通常是一种好习惯,但鉴于数组具有更好的语法,通常会很痛苦。 具体来说, number[]Array<number>的简写形式,就像Date[]Array<Date>的简写形式一样。


TypeScript 3.4使用针对数组类型的新的readonly修饰符为ReadonlyArray引入了新的语法。


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

readonly元组


TypeScript 3.4还引入了对readonly元组的新支持。 我们可以为任何元组类型加上readonly关键字前缀,以使其成为readonly元组,就像我们现在使用数组简写语法一样。 如您所料,与可以写入插槽的普通元组不同, readonly元组仅允许从这些位置读取。


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

普通元组是从Array扩展的类型的方法相同-元素类型为T 1T 2 ,... T n的元组从Array< T 1 |扩展。 T 2 | ... T n > - readonly元组是从ReadonlyArray扩展的类型。 因此,具有元素T 1T 2 ,... T nreadonly元组从ReadonlyArray< T 1 | |中扩展。 T 2 | ... T n >


readonly映射的类型修饰符和readonly数组


在TypeScript的早期版本中,我们对映射类型进行了概括,以对类似数组的类型进行不同的操作。 这意味着像Boxify这样的映射类型可以在数组和元组上工作。


 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]>; 

不幸的是,像Readonly实用程序类型这样的映射类型实际上是数组和元组类型上的无操作。


 // 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]>; 

在TypeScript 3.4中,映射类型中的readonly修饰符会自动将类似数组的类型转换为其对应的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]>; 

类似地,您可以编写一种实用程序类型,例如Writable映射类型,该实用程序类型剥夺了readonly -ness,并将readonly数组容器转换回它们的可变等效项。


 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]>; 

注意事项


尽管有外观,但readonly类型修饰符只能用于数组类型和元组类型的语法。 它不是通用类型的运算符。


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

const断言


在声明可变变量或属性时,TypeScript通常会加宽值,以确保以后可以分配内容而无需编写显式类型。


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

从技术上讲,每个文字值都有一个文字类型。 上面,在为x推断类型之前,类型"hello"被扩展为类型string


一种替代的观点可能是说x具有原始文字类型"hello" ,并且以后我们不能像这样分配"world"


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

在这种情况下,这似乎是极端的,但是在其他情况下它可能会很有用。 例如,TypeScripters经常创建旨在用于区分联合的对象。


 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; } 

可变性是TypeScript可以用来确定何时扩展(而不是分析整个程序)的最佳意图启发法之一。


不幸的是,正如我们在上一个示例中看到的那样,JavaScript中的属性默认是可变的。 这意味着该语言经常会不合需要地扩展类型,从而在某些地方需要显式类型。


 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; } 

在一定程度上这是可以的,但是随着我们的数据结构变得越来越复杂,这变得很麻烦。


为了解决这个问题,TypeScript 3.4引入了一种用于常量值的新构造,称为const断言。 它的语法是类型声明,用const代替类型名称(例如123 as const )。 当我们使用const断言构造新的文字表达式时,我们可以向语言发出信号


  • 该表达式中的文字类型都不应扩展(例如,不要从"hello"string
  • 对象文字获取readonly属性
  • 数组文字变成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; 

.tsx文件之外,也可以使用尖括号声明语法。


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

此功能通常意味着通常会省略那些原本仅用于提示编译器不变的类型。


 // 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); } } 

请注意,上面不需要任何类型注释。 const断言使TypeScript可以采用最具体的表达式类型。


注意事项


需要注意的一件事是, const断言只能立即应用于简单的文字表达式。


 // 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; 

要记住的另一件事是, const上下文不会立即将表达式转换为完全不可变的。


 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检查类型


在全局范围内访问或声明值可能令人惊讶地困难,这可能是因为我们在模块中编写代码(默认情况下,其本地声明不会泄漏),或者因为我们可能有一个遮盖了名称的局部变量。全球价值。 在不同的环境中,有不同的方法可以访问有效的全局范围-在浏览器中的Node, windowselfframes中,或者在严格模式之外的某些位置中,可以全局访问。 这些都不是显而易见的,并且经常使用户不确定他们是否在编写正确的代码。


TypeScript 3.4引入了对类型检查ECMAScript新的globalThis -一个全局变量,它很好地引用了全局范围。 与上述解决方案不同, globalThis提供了访问可在不同环境中使用的全局范围的标准方法。


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

globalThis还可以通过在访问全局变量时将其视为readonly属性来反映是否将全局变量声明为const


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

重要的是要注意,在编译为旧版本的ECMAScript时,TypeScript不会globalThis引用进行转换。 因此,除非您要定位常绿浏览器(已经支持globalThis ),否则您可能要使用适当的polyfill


转换为命名参数


有时,参数列表开始变得笨拙。


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

在上面的示例中,调用者混淆给定参数的顺序太容易了。 常见的JavaScript模式是改为使用“选项对象”,以便每个选项都被明确命名,并且顺序无关紧要。 这模拟了其他语言称为“命名参数”的功能。


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

TypeScript团队不仅在编译器上工作,我们还提供了编辑器用于丰富功能(例如完成,定义和重构)的功能。 在TypeScript 3.4中,我们的实习生Gabriela Britto实现了新的重构,以转换现有功能以使用此“命名参数”模式。


将重构应用于函数以使其采用命名参数。


虽然我们可能会在最终的3.4版本中更改功能的名称,并且我们认为某些人体工程学可能还有余地,但我们希望您尝试一下该功能并提供反馈。


重大变化


顶层,现在输入


现在,此顶级类型的类型为typeof globalThis而不是any 。 结果,在noImplicitAny下访问this值的未知值可能会收到错误。


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

请注意,在noImplicitThis下编译的代码在noImplicitThis不会发生任何更改。


传播的泛型类型参数


在某些情况下,TypeScript 3.4的改进的推断可能会产生通用的函数,而不是接受并返回其约束的函数(通常为{} )。


 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"); 

x上的显式类型注释可以消除该错误。


接下来是什么?


TypeScript 3.4是我们的第一个发行版,其迭代计划概述了此发行版的计划,这旨在与我们的6个月路线图保持一致。 您可以随时关注这两个方面,也可以随时关注我们的滚动功能路线图页面上即将进行的任何工作。


目前,我们期待听到您在RC方面的经验,因此请立即尝试一下,让我们知道您的想法!

Source: https://habr.com/ru/post/zh-CN443996/


All Articles