Categories
程式開發

TypeScript 4.0正式发布!现在是开始使用它的最佳时机


今天,微软宣布 TypeScript 4.0 正式版上线了!这一新版本深入改进了表现力、生产力和可伸缩性,是 TypeScript 语言的新一代版本。

今天,我们很高兴地宣布TypeScript 4.0正式版终于上线了!它是我们深入改进表现力、生产力和可伸缩性的结果,是TypeScript语言的新一代版本。

如果你还不熟悉TypeScript,这里简单介绍一下:它是一种在JavaScript之上通过添加静态类型语法来构建的语言。它的基本理念是,记下值的类型以及它们的使用位置后,可以使用TypeScript对代码进行类型检查,并在运行代码之前(甚至在保存文件之前)告诉你代码错误的相关信息。然后,你可以使用TypeScript编译器从代码中剥离类型,并为你提供可在任何地方运行的简洁易读的JavaScript代码。除了类型检查之外,TypeScript还使用静态类型来支持强大的编辑器工具,例如自动完成、代码导航、重构等。实际上,如果你在Visual Studio Code或Visual Studio这样的编辑器中使用过JavaScript,那么你已经用上了类型和TypeScript带来的体验。可以在我们的网站上了解更多相关信息。

TypeScript 4.0没有引入特别重大的更改。实际上,如果你刚刚开始接触这种语言,那么现在是开始使用它的最佳时机。它的社区已经成熟完善,并在不断发展,拥有可运行的代码和很棒的新资源可供学习。还有一件事情:尽管我们为4.0引入了那么多好东西,但你实际上只需要了解TypeScript的基础知识就可以开始生产应用了!

如果你已经在项目中使用TypeScript,则可以通过NuGet获取它,也可以通过以下命令使用npm获取:

npm install -D typescript 

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

4.0之旅

TypeScript是当今许多人的JavaScript技术栈的核心部分。在npm上,TypeScript在7月首次实现了超过5000万的月下载量!尽管我们知道它总有增长和改进的余地,但很明显,大多数使用TypeScript编码的开发人员确实很喜欢它。StackOverflow的最新开发人员调查将TypeScript列为第二受欢迎的语言。在最新的JS现状调查中,使用TypeScript的开发人员中有大约89%表示会再次使用它。

值得一提的是我们走到今天所走过的旅程。在之前的两个主要版本中,我们回顾了多年来闪耀的一些亮点。对于TypeScript 4.0,我们将保持这种传统。

从3.0版本向前看,可以看到许多令人眼花缭乱的更改,但是TypeScript 3.0本身就产生了很大的冲击。统一元组类型和参数列表是当时的一大亮点,可在函数上启用大量已有的JavaScript模式。这个发行版还提供了项目参考,以帮助扩展、组织和共享代码库。3.0版的一个产生重大影响的小更改是对any引入了类型安全的替代方法,称为unknown。

TypeScript 3.1扩展了映射类型的功能以处理元组和数组类型,并极大简化了将属性附加到函数的过程,而无需使用TypeScript专属的运行时功能(已停用)。

TypeScript 3.2允许对象在泛型类型上传播,并通过严格类型化bind、call和apply,利用3.0的功能更好地建模函数的元编程。TypeScript 3.3更多关注稳定性,但也改进了联合类型方法,并在–build模式下添加了文件增量式构建。

在3.4版本中,我们进一步支持函数式模式,更好地支持不可变数据结构,并改进了对高阶泛型函数的推断。这个发行版的一大改进是引入了–incremental标志,该方法避免了在每次TypeScript运行时完全重建,从而加快了编译和类型检查的速度。

在TypeScript 3.5和3.6中加强了一些类型系统规则,还带来了更智能的兼容性检查规则。

TypeScript 3.7是一个非常值得关注的版本,因为它很好地结合了许多新的类型系统特性与ECMAScript特性。在类型系统方面,我们加入了递归类型别名引用和对断言样式函数的支持,这两者都是独特的类型系统特性。从JavaScript方面来看,该版本带来了可选链和空值合并功能,这是TypeScript和JavaScript用户最期待的两项功能。

最近,3.8和3.9带来了仅类型的导入/导出,以及许多ECMAScript特性,例如私有字段、模块中的顶级await和新的export*语法。这些版本还带来了性能和可伸缩性优化。

我们还没有提到关于语言服务、基础架构、网站和其他核心项目中的那些工作,这些工作对于TypeScript的体验非常关键。核心团队的项目之外,我们在生态系统中还有非常出色的贡献者社区,他们推动了体验的不断改进,并通过DefinitelyTyped甚至TypeScript本身提供了帮助。在2012年刚开始时,DefinitelyTyped仅有80个拉取请求。在2019年,它有超过8300个拉取请求,非常震撼人心。这些贡献是TypeScript体验的基础,这样一个繁忙而热情的社区在不断改善我们的生态系统,并推动我们不断改进,我们对此表示感谢。

新内容

  • 可变元组类型

  • 标记的元组元素

  • 构造器的类属性推断

  • 短路赋值运算符

  • catch子句绑定unknown

  • 定制JSX工厂

  • 加快了带有–noEmitOnError的build模式的速度

  • 带有–noEmit的–incremental

  • 编辑器改进

    • 转换为可选链
    • /** @depreacted */支持
    • 启动时的部分编辑模式
    • 更智能的自动导入
  • 我们的新网站!

  • 重大更改

可变元组类型

考虑JavaScript中称为concat的函数,该函数接收两个数组或元组类型,并将它们连接在一起以创建一个新数组。

function concat(arr1, arr2) { 
    return [...arr1, ...arr2]; 
} 

考虑tail,它接收一个数组或元组,并返回除第一个元素外的所有元素。

function tail(arg) { 
    const [_, ...result] = arg; 
    return result 
} 

我们如何在TypeScript中为它们类型化?
对于concat,我们在较旧版本的TS中唯一可以做的就是尝试编写一些重载。

function concat(arr1: [], arr2: []): [A]; 
function concat(arr1: [A], arr2: []): [A]; 
function concat(arr1: [A, B], arr2: []): [A, B]; 
function concat(arr1: [A, B, C], arr2: []): [A, B, C]; 
function concat(arr1: [A, B, C, D], arr2: []): [A, B, C, D]; 
function concat(arr1: [A, B, C, D, E], arr2: []): [A, B, C, D, E]; 
function concat(arr1: [A, B, C, D, E, F], arr2: []): [A, B, C, D, E, F];) 

第二个数组始终为空时会冒出来七个重载。当arr2有一个参数时我们再加一些看看。

function concat(arr1: [], arr2: [A2]): [A2]; 
function concat(arr1: [A1], arr2: [A2]): [A1, A2]; 
function concat(arr1: [A1, B1], arr2: [A2]): [A1, B1, A2]; 
function concat(arr1: [A1, B1, C1], arr2: [A2]): [A1, B1, C1, A2]; 
function concat(arr1: [A1, B1, C1, D1], arr2: [A2]): [A1, B1, C1, D1, A2]; 
function concat(arr1: [A1, B1, C1, D1, E1], arr2: [A2]): [A1, B1, C1, D1, E1, A2]; 
function concat(arr1: [A1, B1, C1, D1, E1, F1], arr2: [A2]): [A1, B1, C1, D1, E1, F1, A2]; 

很明显这变得越来越离谱了。不幸的是,在类型化tail之类的函数时,你也会遇到同样的问题。
下面是另一种情况,我们称之为“被一千个重载搞垮”,它甚至什么问题都解决不了。它只为我们想写的重载提供正确的类型(不管重载有多少)。如果我们想做一个通行模式,就需要下面这种重载:

function concat(arr1: T[], arr2, U[]): Array; 

但在使用元组时,这个签名不会包含输入长度或元素顺序的任何信息。
TypeScript 4.0带来了两个基础更改,并在推断方面进行了改进,从而可以类型化这些内容。

第一个更改是元组类型语法中的spread现在可以泛型。这意味着即使我们不知道要操作的实际类型,也可以表示对元组和数组的高阶操作。在这些元组类型中实例化泛型spread(或用真实类型替换)时,它们可以产生其他数组和元组类型集。

例如,我们可以类型化tail那样的函数,而不会出现“被一千个重载搞垮”的问题。

function tail(arr: readonly [any, ...T]) { 
    const [_ignored, ...rest] = arr; 
    return rest; 
} 
const myTuple = [1, 2, 3, 4] as const; 
const myArray = ["hello", "world"]; 
// type [2, 3, 4] 
const r1 = tail(myTuple); 
// type [2, 3, ...string[]] 
const r2 = tail([...myTuple, ...myArray] as const); 

第二个更改是,rest元素可以出现在元组中的任何位置,而不仅仅是在结尾!

type Strings = [string, string]; 
type Numbers = [number, number]; 
// [string, string, number, number] 
type StrStrNumNum = [...Strings, ...Numbers]; 

以前,TypeScript会发出如下错误。

A rest element must be last in a tuple type. 

但是现在这种限制取消了。
当我们在没有已知长度的类型中spread时,结果类型也将变得不受限制,并且后面的所有元素都会变为结果的rest元素类型。

type Strings = [string, string]; 
type Numbers = number[] 
// [string, string, ...Array] 
type Unbounded = [...Strings, ...Numbers, boolean]; 

将这两种行为结合在一起,我们可以为concat编写一个类型良好的签名:

type Arr = readonly any[]; 
function concat(arr1: T, arr2: U): [...T, ...U] { 
    return [...arr1, ...arr2]; 
} 

尽管一个签名仍然有些冗长,但它毕竟只有一个,只需写一次,并且在所有数组和元组上都具有可预测的行为。
这个功能很棒,在其他更复杂的场景中更有用。例如,考虑一个函数来部分应用参数,名为partialCall。partialCall接收一个函数(这里就叫f)以及该函数期望的几个初始参数。然后,它返回一个新函数,接收它需要的其他所有参数,收到后调用f。

function partialCall(f, ...headArgs) { 
    return (...tailArgs) => f(...headArgs, ...tailArgs) 
} 

TypeScript 4.0改进了rest参数和rest元组元素的推断过程,因此我们可以类型化它并使其“正常工作”。

type Arr = readonly unknown[]; 
function partialCall(f: (...args: [...T, ...U]) => R, ...headArgs: T) { 
    return (...b: U) => f(...headArgs, ...b) 
} 

在这种情况下,partialCall会知道其最初可以使用和不能使用哪些参数,并返回一个可以正确接收和拒绝剩余内容的函数。

const foo = (x: string, y: number, z: boolean) => {} 
// This doesn't work because we're feeding in the wrong type for 'x'. 
const f1 = partialCall(foo, 100); 
//                          ~~~ 
// error! Argument of type 'number' is not assignable to parameter of type 'string'. 

// This doesn't work because we're passing in too many arguments. 
const f2 = partialCall(foo, "hello", 100, true, "oops") 
//                                              ~~~~~~ 
// error! Expected 4 arguments, but got 5. 

// This works! It has the type '(y: number, z: boolean) => void' 
const f3 = partialCall(foo, "hello"); 
// What can we do with f3 now? 
f3(123, true); // works! 
f3(); 
// error! Expected 2 arguments, but got 0. 
f3(123, "hello"); 
//      ~~~~~~~ 
// error! Argument of type '"hello"' is not assignable to parameter of type 'boolean'. 

可变元组类型创造了许多新模式,尤其是在函数组合方面。我们希望利用它来改善对JavaScript内置的bind方法的类型检查。此外还有其他一些推断改进和模式,想了解更多信息,可以查看可变元组的拉取请求

标记的元组元素

改善元组类型和参数列表的体验很重要,因为它使我们能够围绕常见的JavaScript习惯用法进行强类型验证——实际上只是对参数列表进行切片和切块,并将它们传递给其他函数。对rest参数使用元组类型是其中的关键。

例如,以下函数使用元组类型作为rest参数:

function foo(...args: [string, number]): void { 
    // ... 
} 

……应该与以下函数没有区别……

function foo(arg0: string, arg1: number): void { 
    // ... 
} 

……对于foo的任何调用者。

foo("hello", 42); // works 
foo("hello", 42, true); // error 
foo("hello"); // error 

不过可读性就有区别了。在第一个示例中,我们没有第一个和第二个元素的参数名称。尽管这些对类型检查没有影响,但元组位置上缺少标记会难以传达我们的意图。
因此,在TypeScript 4.0中,元组类型现在可以提供标记。

type Range = [start: number, end: number]; 

为了进一步加强参数列表和元组类型之间的联系,我们让rest元素和可选元素的语法与参数列表的语法一致。

type Foo = [first: number, second?: string, ...rest: any[]]; 

标记元组使用时有些规则,其中一条是:在标记一个元组元素时,还必须标记元组中的所有其他元素。

type Bar = [first: string, number]; 
//                         ~~~~~~ 
// error! Tuple members must all have names or all not have names. 

值得注意的是,在解构时标记不需要我们用不同的名称命名变量。它们纯粹是为文档和工具链服务的。

function foo(x: [first: string, second: number]) { 
    // ... 
    // note: we didn't need to name these 'first' and 'second' 
    let [a, b] = x; 
    // ... 
} 

总的来说,当利用围绕元组和参数列表的模式,并以类型安全的方式实现重载时,带标记的元组非常方便好用。实际上,TypeScript的编辑器支持会在可能的情况下将它们显示为重载。

TypeScript 4.0正式发布!现在是开始使用它的最佳时机 1

了解更多信息,请查看带标记的元组元素的拉取请求

构造器的类属性推断

当启用noImplicitAny时,TypeScript 4.0现在可以使用控制流分析来确定类中属性的类型。

class Square { 
    // Previously: implicit any! 
    // Now: inferred to `number`! 
    area; 
    sideLength; 
    constructor(sideLength: number) { 
        this.sideLength = sideLength; 
        this.area = sideLength ** 2; 
    } 
} 

如果构造器的路径并非都分配给一个实例成员,则该属性可能被认为是undefined的。

class Square { 
    sideLength; 
    constructor(sideLength: number) { 
        if (Math.random()) { 
            this.sideLength = sideLength; 
        } 
    } 
    get area() { 
        return this.sideLength ** 2; 
        //     ~~~~~~~~~~~~~~~ 
        // error! Object is possibly 'undefined'. 
    } 
} 

如果你更了解某些情况(例如,你拥有某种initialize方法),则当你处于strictPropertyInitialization中时,需要使用显式类型注释以及明确的赋值断言(!)。

class Square { 
    // definite assignment assertion 
    //        v 
    sideLength!: number; 
    //         ^^^^^^^^ 
    // type annotation 
    constructor(sideLength: number) { 
        this.initialize(sideLength) 
    } 
    initialize(sideLength: number) { 
        this.sideLength = sideLength; 
    } 
    get area() { 
        return this.sideLength ** 2; 
    } 
} 

更多信息请见拉取请求

短路赋值运算符

JavaScript和许多语言都支持一组称为”复合赋值运算符”的运算符。复合赋值运算符将一个运算符应用于两个参数,然后将结果赋给左侧。你可能以前看过这些:

// Addition 
// a = a + b 
a += b; 
// Subtraction 
// a = a - b 
a -= b; 
// Multiplication 
// a = a * b 
a *= b; 
// Division 
// a = a / b 
a /= b; 
// Exponentiation 
// a = a ** b 
a **= b; 
// Left Bit Shift 
// a = a << b 
a <<= b; 

JavaScript中有很多运算符都有对应的赋值运算符!但是有三个值得注意的例外:逻辑和(&&),逻辑或(||)和空值合并(??)。
所以TypeScript 4.0支持了一个新的ECMAScript特性,添加了三个新的赋值运算符:&&=,||=和??=。

这些运算符非常适合替换下面这种代码示例:

a = a && b; 
a = a || b; 
a = a ?? b; 

或者像下面这样的if代码段:

// could be 'a ||= b' 
if (!a) { 
    a = b; 
} 

我们甚至看到了一些模式,可以在需要时懒惰地初始化值。

let values: string[]; 
// Before 
(values ?? (values = [])).push("hello"); 
// After 
(values ??= []).push("hello"); 

在极少数情况下,你使用带有副作用的getter或setter时,需要注意的是这些运算符仅在必要时执行赋值。从这个意义上讲,"短路"的不仅是运算符的右侧,赋值本身也短路了。

obj.prop ||= foo(); 
// roughly equivalent to either of the following 
obj.prop || (obj.prop = foo()); 
if (!obj.prop) { 
    obj.prop = foo(); 
} 

可以试着运行 这个示例,看看它和总是执行赋值有什么区别。

const obj = { 
    get prop() { 
        console.log("getter has run"); 
        // Replace me! 
        return Math.random() < 0.5; 
    }, 
    set prop(_val: boolean) { 
        console.log("setter has run"); 
    } 
}; 
function foo() { 
    console.log("right side evaluated"); 
    return true; 
} 
console.log("This one always runs the setter"); 
obj.prop = obj.prop || foo(); 
console.log("This one *sometimes* runs the setter"); 
obj.prop ||= foo(); 

有关更多细节可以查看拉取请求。你也可以查看TC39的提案存储库

catch子句绑定支持unknown

自TypeScript诞生以来,catch子句变量始终按any类型化。这意味着TypeScript允许你对它们进行任何操作。

try { 
    // ... 
} 
catch (x) { 
    // x has type 'any' - have fun! 
    console.log(x.message); 
    console.log(x.toUpperCase()); 
    x++; 
    x.yadda.yadda.yadda(); 
} 

上述代码会有一些无法预期的行为!由于这些变量默认情况下的类型为any,因此它们没有任何类型安全性可以防止无效操作。
因此,TypeScript 4.0现在允许你将catch子句变量的类型指定为unknown。unknown比any更安全,因为它会在我们操作值之前提醒我们执行某种类型检查。

try { 
    // ... 
} 
catch (e: unknown) { 
    // error! 
    // Property 'toUpperCase' does not exist on type 'unknown'. 
    console.log(e.toUpperCase()); 
    if (typeof e === "string") { 
        // works! 
        // We've narrowed 'e' down to the type 'string'. 
        console.log(e.toUpperCase()); 
    } 
} 

尽管默认情况下catch变量的类型不会更改,但我们将来可能会考虑使用新的–strict模式标志,以便用户选择启用此行为。同时,应该可以编写一个lint规则来强制catch变量具有如下显式注解之一:: any或: unknown。
有关更多信息,可以查看拉取请求

定制JSX工厂

使用JSX时,fragment是JSX元素的一种,允许我们返回多个子元素。当我们第一次在TypeScript中实现fragment时,我们对其他库如何利用它们并不了解。如今,大多数鼓励使用JSX和支持fragment的库都具有类似的API设计。

在TypeScript 4.0中,用户可以通过新的jsxFragmentFactory选项来自定义fragment工厂。

例如,以下tsconfig.json文件告诉TypeScript以与React兼容的方式转换JSX,但将每个工厂调用(invocation)切换为h而不是React.createElement,并使用Fragment而不是React.Fragment。

{ 
  "compilerOptions": { 
    "target": "esnext", 
    "module": "commonjs", 
    "jsx": "react", 
    "jsxFactory": "h", 
    "jsxFragmentFactory": "Fragment" 
  } 
} 

如果需要基于各个文件使用不同的JSX工厂,则可以利用新的/** @jsxFrag */注释。例如,下面的内容:

// Note: these pragma comments need to be written 
// with a JSDoc-style multiline syntax to take effect. 
/** @jsx h */ 
/** @jsxFrag Fragment */ 
import { h, Fragment } from "preact"; 
let stuff =  
    
Hello
;

将输出成这样的JavaScript:

// Note: these pragma comments need to be written 
// with a JSDoc-style multiline syntax to take effect. 
/** @jsx h */ 
/** @jsxFrag Fragment */ 
import { h, Fragment } from "preact"; 
let stuff = h(Fragment, null, 
    h("div", null, "Hello")); 

查看拉取请求以获取更多信息

加快了带有–noEmitOnError的build模式的速度

以前,使用–noEmitOnError标志时,当先前的编译在–incremental下出现错误,编译速度将非常缓慢。这是因为基于–noEmitOnError标志,上次编译的任何信息都不会缓存在.tsbuildinfo文件中。

TypeScript 4.0对此进行了更改,从而在这些情况下极大地提高了速度,进而改进了–build模式的场景(这意味着同时有–incremental和–noEmitOnError)。

有关详细信息,请查看拉取请求

带有–noEmit的–incremental

TypeScript 4.0允许我们在利用–incremental编译时使用–noEmit标志。以前不允许这样做,因为–incremental需要发出.tsbuildinfo文件。

有关详细信息,请查看拉取请求

编辑器改进

TypeScript编译器不仅可以为大多数主流编辑器提供较好的TS编辑体验,还可以改进Visual Studio系列编辑器的JavaScript开发体验。

根据你使用的编辑器,在编辑器中使用新的TypeScript/JavaScript功能时会有区别:

更多信息见TS编辑器支持列表

转换为可选链

可选链是一项新功能,受到了广泛的欢迎。TypeScript 4.0在转换常见模式时可以利用可选链和空值合并的优势!

TypeScript 4.0正式发布!现在是开始使用它的最佳时机 2

我们认为这种重构应该能捕获大多数用例的意图,尤其是当TypeScript对你的类型有更精确的了解时。

有关详细信息,请查看拉取请求

/** @deprecated */支持

现在,TypeScript的编辑支持可以识别声明中是否带有/** @deprecated*/ JSDoc注释。该信息显示在自动完成列表中,并作为编辑器可以特别处理的建议诊断。在像VSCode这样的编辑器中,deprecated的值通常显示为删除线样式。

TypeScript 4.0正式发布!现在是开始使用它的最佳时机 3

有关详细信息,查看拉取请求

启动时的部分编辑模式

很多用户抱怨启动时间缓慢,尤其是在大型项目中。具体来说,罪魁祸首通常是一个称为项目加载的过程,该过程与我们编译器的程序构建步骤大致相同。这一过程从一组初始文件开始,解析它们、解析它们的依赖、再解析那些依赖,解析那些依赖的依赖,等等,最后需要花费很长时间。项目越大,启动延迟可能会越长。

所以我们一直在努力为开发人员提供一种新的模式,在获得完整的语言服务体验之前提供部分体验。这里的核心思想是,编辑者可以运行仅具有单个文件视图的轻量级部分服务器。

这种新模式可以将TypeScript在代码库上开始交互之前的准备时间从20秒到1分钟缩短到只有几秒钟。比如说,在较大的代码库上重启编辑器时,TS 3.9版没法立即提供自动完成和快速信息;另一方面,TS 4.0可以立即提供完整的编辑体验,同时在后台加载整个项目。

当前,唯一支持此模式的编辑器是Visual Studio Code,但UX和功能仍有改进的余地。我们列出了准备加入的改进,希望获得更多反馈。

有关更多信息,你可以查看原始提案拉取请求,以及后续的meta问题

更智能的自动导入

自动导入是一个了不起的功能。但是,自动导入在用TypeScript编写的包上不起作用——也就是说,我们得在项目的其他位置至少写了一个显式导入。

为什么自动导入适用于@types软件包,而不适用于使用自己类型的包呢?其实自动导入是通过检查项目中已经包含的软件包来实现的。TypeScript有一个怪癖,可以自动包括node_modules/@types中的所有包,而忽略其他包;但爬取所有node_modules包的开销可能会很昂贵。

当你尝试自动导入刚刚安装但尚未使用的内容时,这些都会导致糟糕的体验。

TypeScript 4.0现在可以包含你在package.json的dependencies(和peerDependencies)字段中列出的包。这些包中的信息仅用于改进自动导入,不会更改类型检查等其他内容。这样就避免了遍历node_modules目录的成本,使我们可以为所有带类型的依赖项提供自动导入。

当你的package.json列出了超过十项尚未导入的类型化依赖项时,这个功能会自动禁用,以避免缓慢的项目加载过程。要强制开启它或完全禁用它,你可以配置编辑器。在Visual Studio Code中是"Include Package JSON Auto Imports"设置(或typescript.preferences.includePackageJsonAutoImports)。

TypeScript 4.0正式发布!现在是开始使用它的最佳时机 4

有关详细信息,可以查看提案问题以及拉取请求

我们的新网站!

TypeScript网站最近被彻底重写了!

TypeScript 4.0正式发布!现在是开始使用它的最佳时机 5

详细信息可以参考之前的文章:

《TypeScript 新版网站上线:带来了新的导航机制》

重大更改

lib.d.ts

我们的lib.d.ts声明已更改,具体来说是DOM的类型已更改。主要是删除了document.origin,它仅在IE的旧版本中有效,而Safari MDN建议改用self.origin。

属性重写访问器(反之亦然)是错误

以前,只有在使用useDefineForClassFields时,属性重写访问器或访问器重写属性是一个错误;但现在,在派生类中声明一个将重写基类中的getter或setter的属性时总是发出错误。

class Base { 
    get foo() { 
        return 100; 
    } 
    set foo() { 
        // ... 
    } 
} 
class Derived extends Base { 
    foo = 10; 
//  ~~~ 
// error! 
// 'foo' is defined as an accessor in class 'Base', 
// but is overridden here in 'Derived' as an instance property. 
} 
class Base { 
    prop = 10; 
} 
class Derived extends Base { 
    get prop() { 
    //  ~~~~ 
    // error! 
    // 'prop' is defined as a property in class 'Base', but is overridden here in 'Derived' as an accessor. 
        return 100; 
    } 
} 

有关详细信息,查看拉取请求

delete的操作数必须是可选的。

在strictNullChecks中使用delete运算符时,操作数现在必须为any、unknown、never或为可选(因为它在类型中包含undefined)。否则,使用delete运算符是错误的。

interface Thing { 
    prop: string; 
} 
function f(x: Thing) { 
    delete x.prop; 
    //     ~~~~~~ 
    // error! The operand of a 'delete' operator must be optional. 
} 

关于更多信息,查看拉取请求

TypeScript的Node工厂用法已弃用

如今,TypeScript提供了一组用于生成AST节点的“工厂”函数。但是,TypeScript 4.0提供了新的node工厂API。因此TypeScript 4.0决定弃用使用这些旧函数,推荐改用新函数。

有关更多信息,请查看拉取请求

下一步计划

TypeScript 4.1的迭代计划已经上线了,你可以大致了解一下。同时,你可以在工作区或编辑器中使用nightly构建来预览4.1中添加的新特性。无论你是在使用TypeScript 4.0还是下一版本,我们都希望听到你的反馈!可以通过Twitter联系我们,或在GitHub上发起问题

我们再一次为社区所做的一切工作和奉献精神深表感谢。我们希望让TypeScript和JavaScript的编码体验成为你应得的纯粹乐趣。为此,我们需要改善语言和编辑体验、提升性能、迭代我们的用户体验、降低入门和学习的门槛等等。

非常感谢大家,请享用4.0版本吧,编程愉快!

原文链接:

https://devblogs.microsoft.com/typescript/announcing-typescript-4-0/