本文译于:TypeScript团队
前言
我们很高兴宣布TypeScript 3.7的发布,该版本包含了很棒的新语言,编译器和工具功能。
如果您还没有听说过TypeScript,那么它是一种基于JavaScript的语言,它添加了静态类型检查以及类型语法。静态类型检查可以让我们知道代码的问题,然后再尝试报告可疑的错误,从而尝试运行它。范围包括可能在代码(如42 / "hello",甚至属性名称的基本错字)中发生的类型强制。但是除此之外,TypeScript还支持一些您喜欢的编辑器中的TypeScript 和 JavaScript的完成,快速修复和重构等功能。实际上,如果您已经在使用Visual Studio或Visual Studio Code,则在编写JavaScript代码时可能已经在使用TypeScript!因此,如果您有兴趣了解更多信息,请访问我们的网站。
如果您已经准备好使用TypeScript,则可以通过NuGet获得它,或者通过以下命令使用npm:
npm install typescript
您还可以通过以下方式获得编辑器支持
- 下载Visual Studio 2019/2017
- 遵循有关Visual Studio代码的说明
- 通过PackageControl升华文本3。
我们在TypeScript 3.7中拥有很多很棒的功能,包括:
- 可选链接
- 空位合并
- 断言功能
- 更好地支持- never返回功能
- --declaration 和 --allowJs
- (更多)递归类型别名
- 该useDefineForClassFields标志和declare属性修改器
- 使用项目参考进行免生成编辑
- 未调用的功能检查
- 奉承错误报告
- // @ts-nocheck 在TypeScript文件中
- 分号格式化选项
- 网站和游乐场更新
- 重大变化DOM变更类字段缓解功能真实检查本地和导入类型声明现在发生冲突API变更
这是一个相当广泛的清单!如果您正在阅读,那么此版本将为您带来一些乐趣。但是,如果您是喜欢通过弄脏双手来学习的人,请查看TypeScript游乐场,我们在其中添加了整个菜单以了解新功能。
事不宜迟,让我们深入了解新功能!
可选链接
TypeScript 3.7实现了迄今为止最需要的ECMAScript功能之一:可选链接!
可选链接是问题跟踪器上的问题16。就上下文而言,迄今为止,在TypeScript问题跟踪器中已提交了23,000多个问题。该文件是在5年前提交的,当时TC39甚至还没有正式的提案。多年来,我们一直被要求实现该功能,但长期以来我们的立场一直是与潜在的ECMAScript建议不冲突。取而代之的是,我们的团队最近采取了一些措施,以帮助推动该提案实现标准化,并最终推动所有JavaScript和TypeScript用户。实际上,我们参与到支持该提案的地步!随着第3阶段的进展,我们很高兴并自豪地将其作为TypeScript 3.7的一部分发布。
那么什么是可选链接?从本质上讲,可选链接使我们可以编写代码,如果遇到a null或,我们可以立即停止运行某些表达式undefined。可选链接中的演出明星是可选属性访问的新?.运算符。当我们写类似的代码
let x = foo?.bar.baz();
这就是说,何时foo定义,foo.bar.baz()将被计算;但是当foo是null或时undefined,停止我们在做的事情,然后回来undefined。
更明确地说,该代码段与编写以下代码相同。
let x = (foo === null || foo === undefined) ?
undefined :
foo.bar.baz();
请注意,如果bar是null或undefined,我们的代码访问仍然会出错baz。同样,如果baz是null或undefined,我们将在呼叫站点出现错误。?.只能对值是否检查左侧的是null或undefined-不属于任何后续的属性。
您可能会发现自己?.用来替换许多使用&&运算符执行重复空位检查的代码。
// Before
if (foo && foo.bar && foo.bar.baz) {
// ...
}
// After-ish
if (foo?.bar?.baz) {
// ...
}
请记住,?.行为不同于那些&&操作,因为&&将专门作用于“falsy”值(如空字符串,0,NaN,,嗯,false),但是这是构建的有意功能。它不会使有效数据(如0空字符串)短路。
可选链接还包括其他两个操作。首先是可选元素访问,其作用类似于可选属性访问,但允许我们访问非标识符属性(例如,任意字符串,数字和符号):
/** * Get the first element of the array if we have an array. * Otherwise return undefined. */function tryGetFirstElement(arr?: T[]) {
return arr?.[0];
// equivalent to
// return (arr === null || arr === undefined) ?
// undefined :
// arr[0];
}
还有一个可选的调用,它允许我们有条件地调用表达式,如果它们不是nullor undefined。
async function makeRequest(url: string, log?: (msg: string) => void) {
log?.(`Request started at ${new Date().toISOString()}`);
// roughly equivalent to
// if (log != null) {
// log(`Request started at ${new Date().toISOString()}`);
// }
const result = (await fetch(url)).json();
log?.(`Request finished at at ${new Date().toISOString()}`);
return result;
}
可选链的“短路”行为是有限的属性访问,调用,元素访问-它不会从这些表达式进一步扩展。换一种说法,
let result = foo?.bar / someComputation()
不会阻止分裂或someComputation()呼叫的发生。相当于
let temp = (foo === null || foo === undefined) ?
undefined :
foo.bar;
let result = temp / someComputation();
这可能会导致除法undefined,这就是为什么在strictNullChecks中的错误。
function barPercentage(foo?: { bar: number }) {
return foo?.bar / 100;
// ~~~~~~~~
// Error: Object is possibly undefined.
}
更多详细信息,您可以阅读该提案并查看原始的拉取请求。
空位合并
该nullish合并运算符是另一个即将到来的ECMAScript的功能,去手牵手与可选的链接,以及我们的团队已经参与倡导。
您可以考虑使用此功能- ??运算符-作为在处理null或时“回退”为默认值的方法undefined。当我们写类似的代码
let x = foo ?? bar();
这是一种新的表示值foo“存在”时将被使用的方式;但是当它是null或时undefined,请计算bar()其位置。
同样,以上代码等效于以下代码。
let x = (foo !== null && foo !== undefined) ?
foo :
bar();
尝试使用默认值时,??操作员可以替换使用||。例如,以下代码片段尝试获取上次保存的卷localStorage(如果曾经保存过);但是,由于使用,因此存在一个错误||。
function initializeAudio() {
let volume = localStorage.volume || 0.5
// ...
}
当localStorage.volume设置为时0,页面将设置0.5意外的音量。??避免了一些意外情况0,NaN以及""被视为falsy值。
我们非常感谢社区成员Wang Wenlu和Titian Cernicova Dragomir实施此功能!有关更多详细信息,请查看其拉取请求和无效的合并提案存储库。
断言功能
throw如果发生意外情况,则有一组特定的函数会出错。它们被称为“断言”功能。例如,Node.js为此有一个专用功能assert。
assert(someValue === 42);
在此示例中,如果someValue不等于42,assert则将抛出AssertionError。
JavaScript中的断言通常用于防止传入不正确的类型。例如,
function multiply(x, y) {
assert(typeof x === "number");
assert(typeof y === "number");
return x * y;
}
不幸的是,在TypeScript中,这些检查永远无法正确编码。对于松散类型的代码,这意味着TypeScript的检查较少,而对于稍微保守的代码,则通常迫使用户使用类型断言。
function yell(str) {
assert(typeof str === "string");
return str.toUppercase();
// Oops! We misspelled 'toUpperCase'.
// Would be great if TypeScript still caught this!
}
替代方法是改写代码,以便语言可以对其进行分析,但这并不方便。
function yell(str) {
if (typeof str !== "string") {
throw new TypeError("str should have been a string.")
}
// Error caught!
return str.toUppercase();
}
最终,TypeScript的目标是以最小的破坏性方式键入现有的JavaScript结构。因此,TypeScript 3.7引入了一个称为“断言签名”的新概念,可以对这些断言函数进行建模。
第一种断言签名对节点assert功能的工作方式进行建模。它确保在包含范围的其余部分中,无论检查什么条件都必须为真。
function assert(condition: any, msg?: string): asserts condition {
if (!condition) {
throw new AssertionError(msg)
}
}
asserts condition表示,condition如果assert返回则传递给参数的任何内容都必须为true (因为否则会引发错误)。这意味着对于其余范围,该条件必须是真实的。举个例子,使用这个断言函数意味着我们确实抓住了我们原来的yell例子。
function yell(str) {
assert(typeof str === "string");
return str.toUppercase();
// ~~~~~~~~~~~
// error: Property 'toUppercase' does not exist on type 'string'.
// Did you mean 'toUpperCase'?
}
function assert(condition: any, msg?: string): asserts condition {
if (!condition) {
throw new AssertionError(msg)
}
}
断言签名的另一种类型不检查条件,而是告诉TypeScript特定的变量或属性具有不同的类型。
function assertIsString(val: any): asserts val is string {
if (typeof val !== "string") {
throw new AssertionError("Not a string!");
}
}
此处asserts val is string确保在调用以后assertIsString,传入的任何变量都将称为string。
function yell(str: any) {
assertIsString(str);
// Now TypeScript knows that 'str' is a 'string'.
return str.toUppercase();
// ~~~~~~~~~~~
// error: Property 'toUppercase' does not exist on type 'string'.
// Did you mean 'toUpperCase'?
}
这些断言签名与编写类型谓词签名非常相似:
function isString(val: any): val is string {
return typeof val === "string";
}
function yell(str: any) {
if (isString(str)) {
return str.toUppercase();
}
throw "Oops!";
}
就像类型谓词签名一样,这些断言签名也具有难以置信的表现力。我们可以用这些表达一些相当复杂的想法。
function assertIsDefined(val: T): asserts val is NonNullable {
if (val === undefined || val === null) {
throw new AssertionError(
`Expected 'val' to be defined, but received ${val}`
);
}
}
要了解有关断言签名的更多信息,请查看原始的pull request。
更好地支持- never返回功能
作为断言签名工作的一部分,TypeScript需要对调用位置和调用函数进行更多编码。这使我们有机会扩展对另一类功能的支持:return的功能never。
任何返回的函数的意图never是它永远不会返回。它表明引发了异常,发生了暂停错误条件或程序已退出。例如,将process.exit(...)in@types/node指定为return never。
为了确保函数永远不会undefined从所有代码路径中返回或有效地返回,TypeScript需要一些语法信号-a return或throw函数末尾。因此,用户发现了自己return的故障功能。
function dispatch(x: string | number): SomeType {
if (typeof x === "string") {
return doThingWithString(x);
}
else if (typeof x === "number") {
return doThingWithNumber(x);
}
return process.exit(1);
}
现在,当never调用这些返回函数时,TypeScript会识别出它们会影响控制流程图并说明原因。
function dispatch(x: string | number): SomeType {
if (typeof x === "string") {
return doThingWithString(x);
}
else if (typeof x === "number") {
return doThingWithNumber(x);
}
process.exit(1);
}
与断言函数一样,您可以在相同的pull request中阅读更多内容。
--declaration 和 --allowJs
--declarationTypeScript中的标志允许我们.d.ts从TypeScript源文件(即.ts和.tsx文件)生成文件(声明文件)。这些.d.ts文件很重要,原因有两个。
首先,它们很重要,因为它们允许TypeScript对其他项目进行类型检查,而无需重新检查原始源代码。它们也很重要,因为它们允许TypeScript与未考虑TypeScript构建的现有JavaScript库进行互操作。最后,通常没有被充分认识到的好处:当使用由TypeScript驱动的编辑器来获得更好的自动完成功能时,TypeScript 和 JavaScript用户都可以从这些文件中受益。
不幸的是,--declaration不能与--allowJs允许混合TypeScript和JavaScript输入文件的标志一起使用。这是一个令人沮丧的限制,因为它意味着用户--declaration即使在迁移代码库时也无法使用该标志,即使使用JSDoc注释也是如此。TypeScript 3.7对此进行了更改,并允许将这两个选项一起使用!
此功能最有影响力的结果可能有点微妙:使用TypeScript 3.7,用户可以使用JSDoc注释的JavaScript编写库并支持TypeScript用户。
它的工作方式是在使用时allowJs,TypeScript会尽最大努力进行分析以了解常见的JavaScript模式。但是,用JavaScript表示某些模式的方式不一定看起来像它们在TypeScript中的等效形式。当declarationEMIT开启时,打字稿数字是最好的出路改造JSDoc意见和CommonJS的出口为有效的类型声明之类的输出.d.ts文件。
例如,以下代码片段
const assert = require("assert")
module.exports.blurImage = blurImage;
/**
* Produces a blurred image from an input buffer.
*
* @param input {Uint8Array}
* @param width {number}
* @param height {number}
*/
function blurImage(input, width, height) {
const numPixels = width * height * 4;
assert(input.length === numPixels);
const result = new Uint8Array(numPixels);
// TODO
return result;
}
会产生一个.d.ts像
/**
* Produces a blurred image from an input buffer.
*
* @param input {Uint8Array}
* @param width {number}
* @param height {number}
*/
export function blurImage(input: Uint8Array, width: number, height: number): Uint8Array;
@param以下示例也可以超越带有标签的基本功能:
/**
* @callback Job
* @returns {void}
*/
/** Queues work */
export class Worker {
constructor(maxDepth = 10) {
this.started = false;
this.depthLimit = maxDepth;
/**
* NOTE: queued jobs may add more items to queue
* @type {Job[]}
*/
this.queue = [];
}
/**
* Adds a work item to the queue
* @param {Job} work
*/
push(work) {
if (this.queue.length + 1 > this.depthLimit) throw new Error("Queue full!");
this.queue.push(work);
}
/**
* Starts the queue if it has not yet started
*/
start() {
if (this.started) return false;
this.started = true;
while (this.queue.length) {
/** @type {Job} */(this.queue.shift())();
}
return true;
}
}
将转换为以下.d.ts文件:
/**
* @callback Job
* @returns {void}
*/
/** Queues work */
export class Worker {
constructor(maxDepth?: number);
started: boolean;
depthLimit: number;
/**
* NOTE: queued jobs may add more items to queue
* @type {Job[]}
*/
queue: Job[];
/**
* Adds a work item to the queue
* @param {Job} work
*/
push(work: Job): void;
/**
* Starts the queue if it has not yet started
*/
start(): boolean;
}
export type Job = () => void;
请注意,将这些标志一起使用时,TypeScript不一定必须降级.js文件。如果仅希望TypeScript创建.d.ts文件,则可以使用--emitDeclarationOnly编译器选项。
有关更多详细信息,您可以签出原始拉取请求。
(更多)递归类型别名
类型别名在如何“递归”引用它们方面一直受到限制。原因是对类型别名的任何使用都必须能够用其别名来代替自己。在某些情况下,这是不可能的,因此编译器会拒绝某些递归别名,如下所示:
type Foo = Foo;
这是一个合理的限制,因为对的任何使用Foo都必须Foo替换为Foo,而必须替换为Foo……那么,希望您能理解!最后,没有一种可以代替的类型Foo。
这与其他语言对待类型别名的方式是相当一致的,但是对于用户如何利用该功能确实引起了一些令人惊讶的场景。例如,在TypeScript 3.6和更低版本中,以下导致错误。
type ValueOrArray= T | Array >;
// ~~~~~~~~~~~~
// error: Type alias 'ValueOrArray' circularly references itself.
这很奇怪,因为从技术上讲,任何使用都没有错,用户总是可以通过引入接口来编写实际上是相同代码的代码。
type ValueOrArray= T | ArrayOfValueOrArray ;interface ArrayOfValueOrArray extends Array > {}
因为接口(和其他对象类型)引入了一个间接级别,并且不需要急切地构建它们的完整结构,所以TypeScript在使用这种结构时没有问题。
但是,引入界面的解决方法对于用户而言并不直观。原则上ValueOrArray,Array直接使用的原始版本确实没有任何问题。如果编译器有点“懒惰”,并且仅Array在必要时才计算type参数,则TypeScript可以正确表达这些参数。
这正是TypeScript 3.7引入的。在类型别名的“顶层”,TypeScript将推迟解析类型参数以允许使用这些模式。
这意味着类似以下代码的代码试图表示JSON…
type Json = | string | number | boolean | null | JsonObject | JsonArray;interface JsonObject { [property: string]: Json;}interface JsonArray extends Array{}
最终可以在没有辅助接口的情况下进行重写。
type Json =
| string
| number
| boolean
| null
| { [property: string]: Json }
| Json[];
这种新的放松也使我们也可以在元组中递归引用类型别名。以下曾经出错的代码现在是有效的TypeScript代码。
type VirtualNode =
| string
| [string, { [key: string]: any }, ...VirtualNode[]];
const myNode: VirtualNode =
["div", { id: "parent" },
["div", { id: "first-child" }, "I'm the first child"],
["div", { id: "second-child" }, "I'm the second child"]
];
有关更多信息,您可以阅读原始的拉取请求。
该useDefineForClassFields标志和declare属性修改器
返回当TypeScript实现公共类字段时,我们尽力做到了以下代码
class C {
foo = 100;
bar: string;
}
等效于构造函数体内的类似分配。
class C {
constructor() {
this.foo = 100;
}
}
不幸的是,虽然这似乎是该提案在早期的发展方向,但极有可能将公共类领域进行不同的标准化。取而代之的是,原始代码示例可能需要对以下内容进行脱糖处理:
class C {
constructor() {
Object.defineProperty(this, "foo", {
enumerable: true,
configurable: true,
writable: true,
value: 100
});
Object.defineProperty(this, "bar", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
}
}
虽然TypeScript 3.7默认不会更改任何现有的发射,但我们一直在逐步推出更改,以帮助用户减轻将来可能发生的损坏。我们提供了一个新的标志,useDefineForClassFields以使用一些新的检查逻辑来启用此发射模式。
最大的两个变化如下:
- 声明使用初始化Object.defineProperty。
- 即使声明没有初始化程序,也始终将其undefined初始化为。
对于使用继承的现有代码,这可能会导致很多后果。首先,set基类的访问器不会被触发-它们将被完全覆盖。
class Base {
set data(value: string) {
console.log("data changed to " + value);
}
}
class Derived extends Base {
// No longer triggers a 'console.log'
// when using 'useDefineForClassFields'.
data = 10;
}
其次,使用类字段来专门化基类的属性也不起作用。
interface Animal { animalStuff: any }
interface Dog extends Animal { dogStuff: any }
class AnimalHouse {
resident: Animal;
constructor(animal: Animal) {
this.resident = animal;
}
}
class DogHouse extends AnimalHouse {
// Initializes 'resident' to 'undefined'
// after the call to 'super()' when
// using 'useDefineForClassFields'!
resident: Dog;
constructor(dog: Dog) {
super(dog);
}
}
这两个问题归结为,将属性与访问器混合将导致问题,因此将在不使用初始化程序的情况下重新声明属性。
为了检测访问器周围的问题,TypeScript 3.7现在将在文件中发出get/ set访问器,.d.ts以便TypeScript可以检查覆盖的访问器。
受类字段更改影响的代码可以通过将字段初始化程序转换为构造函数主体中的分配来解决此问题。
class Base {
set data(value: string) {
console.log("data changed to " + value);
}
}
class Derived extends Base {
constructor() {
this.data = 10;
}
}
为了帮助缓解第二个问题,您可以添加一个显式的初始化程序或添加一个declare修饰符,以指示属性不应该发出。
interface Animal { animalStuff: any }
interface Dog extends Animal { dogStuff: any }
class AnimalHouse {
resident: Animal;
constructor(animal: Animal) {
this.resident = animal;
}
}
class DogHouse extends AnimalHouse {
declare resident: Dog;
// ^^^^^^^
// 'resident' now has a 'declare' modifier,
// and won't produce any output code.
constructor(dog: Dog) {
super(dog);
}
}
当前useDefineForClassFields仅在面向ES5和更高版本时可用,因为Object.definePropertyES3中不存在。为了实现类似的问题检查,您可以创建一个针对ES5的单独项目,并使用它--noEmit来避免进行完整构建。
有关更多信息,您可以查看这些更改的原始拉取请求。
我们强烈建议用户尝试使用该useDefineForClassFields标志,并在我们的问题跟踪器或以下评论中进行报告。这包括有关采用该标志的难度的反馈,因此我们可以了解如何使迁移更容易。
使用项目参考进行免生成编辑
TypeScript的项目参考为我们提供了一种简单的方法来分解代码库,从而使我们可以更快地进行编译。不幸的是,编辑尚未建立依赖关系(或输出过时)的项目意味着编辑体验无法正常工作。
在TypeScript 3.7中,当打开具有依赖项的项目时,TypeScript将自动使用源.ts/ .tsx文件代替。这意味着使用项目引用的项目现在将获得改进的编辑体验,其中语义操作是最新的并且“有效”。您可以使用编译器选项禁用此行为,disableSourceOfProjectReferenceRedirect当在非常大的项目中使用该选项可能会影响编辑性能时,该选项可能是适当的。
您可以通过阅读其拉取请求来阅读有关此更改的更多信息。
未调用的功能检查
一个常见且危险的错误是忘记调用函数,尤其是当函数具有零参数或以暗示它可能是属性而不是函数的方式命名时。
interface User {
isAdministrator(): boolean;
notify(): void;
doNotDisturb?(): boolean;
}
// later...
// Broken code, do not use!
function doAdminThing(user: User) {
// oops!
if (user.isAdministrator) {
sudo();
editTheConfiguration();
}
else {
throw new AccessDeniedError("User is not an admin");
}
}
在这里,我们忘记了调用isAdministrator,该代码错误地允许非管理员用户编辑配置!
在TypeScript 3.7中,这被标识为可能的错误:
function doAdminThing(user: User) {
if (user.isAdministrator) {
// ~~~~~~~~~~~~~~~~~~~~
// error! This condition will always return true since the function is always defined.
// Did you mean to call it instead?
此检查是一项重大更改,但是由于这个原因,检查非常保守。仅在if有条件的情况下才会发出此错误,并且如果strictNullChecks关闭或在后面的函数中调用此函数,则不会在可选属性中发出该错误if:
interface User {
isAdministrator(): boolean;
notify(): void;
doNotDisturb?(): boolean;
}
function issueNotification(user: User) {
if (user.doNotDisturb) {
// OK, property is optional
}
if (user.notify) {
// OK, called the function
user.notify();
}
}
如果您打算在不调用函数的情况下对其进行测试,则可以将其定义更正为undefined/ null,或者使用!!类似的写法if (!!user.isAdministrator)表明强制是有意的。
我们要非常感谢GitHub用户@jwbay,他主动创建了概念验证并反复为我们提供了最新版本。
奉承错误报告
有时,非常简单的代码会导致TypeScript中错误消息的漫长金字塔。例如,此代码
type SomeVeryBigType = { a: { b: { c: { d: { e: { f(): string } } } } } }
type AnotherVeryBigType = { a: { b: { c: { d: { e: { f(): number } } } } } }
declare let x: SomeVeryBigType;
declare let y: AnotherVeryBigType;
y = x;
在早期版本的TypeScript中导致以下错误消息:
Type 'SomeVeryBigType' is not assignable to type 'AnotherVeryBigType'.
Types of property 'a' are incompatible.
Type '{ b: { c: { d: { e: { f(): string; }; }; }; }; }' is not assignable to type '{ b: { c: { d: { e: { f(): number; }; }; }; }; }'.
Types of property 'b' are incompatible.
Type '{ c: { d: { e: { f(): string; }; }; }; }' is not assignable to type '{ c: { d: { e: { f(): number; }; }; }; }'.
Types of property 'c' are incompatible.
Type '{ d: { e: { f(): string; }; }; }' is not assignable to type '{ d: { e: { f(): number; }; }; }'.
Types of property 'd' are incompatible.
Type '{ e: { f(): string; }; }' is not assignable to type '{ e: { f(): number; }; }'.
Types of property 'e' are incompatible.
Type '{ f(): string; }' is not assignable to type '{ f(): number; }'.
Types of property 'f' are incompatible.
Type '() => string' is not assignable to type '() => number'.
Type 'string' is not assignable to type 'number'.
该错误信息是正确的,但最终通过重复文字的墙壁恐吓用户。我们想要了解的最终知识被关于如何达到特定类型的所有信息所掩盖。
[我们反复讨论想法])(microsoft / TypeScript / issues / 33361),所以现在在TypeScript 3.7中,此类错误被扁平化为以下消息:
Type 'SomeVeryBigType' is not assignable to type 'AnotherVeryBigType'.
The types returned by 'a.b.c.d.e.f()' are incompatible between these types.
Type 'string' is not assignable to type 'number'.
有关更多详细信息,您可以查看原始PR。
// @ts-nocheck 在TypeScript文件中
TypeScript 3.7允许我们在// @ts-nocheckTypeScript文件的顶部添加注释以禁用语义检查。从历史上看,只有在存在时checkJs,此注释才在JavaScript源文件中得到尊重,但我们已经扩展了对TypeScript文件的支持,以使所有用户的迁移更加容易。
分号格式化选项
由于JavaScript的自动分号插入(ASI)规则,TypeScript的内置格式化程序现在支持在分号结尾可选的位置插入和删除分号。该设置现在在Visual Studio Code Insiders中可用,在Visual Studio 16.4 Preview 2中的“工具选项”菜单中可用。
选择“插入”或“删除”的值还会影响自动导入的格式,提取的类型以及TypeScript服务提供的其他生成的代码。将设置保留为默认值“ ignore”会使生成的代码与当前文件中检测到的分号首选项相匹配。
网站和游乐场更新
我们将在不久的将来更多地讨论此问题,但是如果您还没有看到它,则应该查看经过重大升级的TypeScript运动场,该运动场现在包括了一些很棒的新功能,例如快速修复错误,暗/高对比度的功能模式和自动类型获取,因此您可以导入其他包!最重要的是,此处的每个功能都通过“新功能”菜单下的交互式代码段进行了说明。
首先,在手册之外,我们现在在网站上提供由Algolia支持的搜索,使您可以搜索手册,发行说明等!
重大变化
DOM变更
中的类型lib.dom.d.ts已更新。这些更改很大程度上是与可空性相关的正确性更改,但是影响最终取决于您的代码库。
类字段缓解
如上所述,TypeScript 3.7会在文件中发出get/ set访问器,.d.ts这可能会导致消费者对3.5和更低版本的TypeScript的旧版本进行重大更改。TypeScript 3.6用户将不会受到影响,因为该功能已针对该版本进行了过时验证。
虽然本身不是破损,但在以下情况下选择使用useDefineForClassFields标志可能会导致破损:
- 用属性声明覆盖派生类中的访问器
- 在没有初始化程序的情况下重新声明属性声明
要了解全部影响,请阅读上面有关useDefineForClassFields标志的部分。
功能真实检查
如上所述,当在if语句条件内似乎未调用函数时,TypeScript现在会出错。在if条件中检查功能类型时,将发出错误,除非满足以下任何条件:
- 检查值来自可选属性
- strictNullChecks 被禁用
- 该函数稍后会在 if
本地和导入类型声明现在发生冲突
由于存在错误,TypeScript以前允许以下构造:
// ./someOtherModule.ts
interface SomeType {
y: string;
}
// ./myModule.ts
import { SomeType } from "./someOtherModule";
export interface SomeType {
x: number;
}
function fn(arg: SomeType) {
console.log(arg.x); // Error! 'x' doesn't exist on 'SomeType'
}
在这里,SomeType似乎起源于import声明和本地interface声明。也许令人惊讶的是,在模块内部,它仅SomeType引用imported定义,而本地声明SomeType仅在从另一个文件导入时才可用。这非常令人困惑,我们对极少数这种情况的代码进行的野蛮审查表明,开发人员通常认为正在发生一些不同的事情。
在TypeScript 3.7中,现在可以正确地将其标识为重复标识符error。正确的解决方案取决于作者的初衷,并应逐案解决。通常,命名冲突是无意的,最好的解决方法是重命名导入的类型。如果要扩展导入的类型,则应编写适当的模块扩展。
API变更
为了启用上述递归类型别名模式,typeArguments已从TypeReference接口中删除了该属性。用户应该getTypeArguments在TypeChecker实例上使用该功能。
下一步是什么?
在享受TypeScript 3.7的同时,您可以浏览TypeScript 3.8的功能!我们最近发布了TypeScript 3.8迭代计划,随着更多细节的融合,我们将更新滚动功能路线图。
结语
我们希望用户在编写代码时能真正感到高兴,我们希望TypeScript 3.7能够做到这一点。尽情享受,祝黑客愉快!
– Daniel Rosenwasser和TypeScript团队