TypeScript 4.6
允许在构造函数中的 super() 调用之前插入代码
在 JavaScript 的类中,在引用 this 之前必须先调用 super()。
在 TypeScript 中同样有这个限制,只不过在检查时过于严格。
在之前版本的 TypeScript 中,如果类中存在属性初始化器,
那么在构造函数里,在 super() 调用之前不允许出现任何其它代码。
class Base {
  // ...
}
class Derived extends Base {
  someProperty = true;
  constructor() {
    // 错误!
    // 必须先调用 'super()' 因为需要初始化 'someProperty'。
    doSomeStuff();
    super();
  }
}
这样做是因为程序实现起来容易,但这样做也会拒绝很多合法的代码。
TypeScript 4.6 放宽了限制,它允许在 super() 之前出现其它代码,
与此同时仍然会检查在引用 this 之前顶层的super() 已经被调用。
感谢 Joshua Goldberg 的 PR。
基于控制流来分析解构的可辨识联合类型
TypeScript 可以根据判别式属性来细化类型。
例如,在下面的代码中,TypeScript 能够在检查 kind 的类型后细化 action 的类型。
type Action =
  | { kind: 'NumberContents'; payload: number }
  | { kind: 'StringContents'; payload: string };
function processAction(action: Action) {
  if (action.kind === 'NumberContents') {
    // `action.payload` is a number here.
    let num = action.payload * 2;
    // ...
  } else if (action.kind === 'StringContents') {
    // `action.payload` is a string here.
    const str = action.payload.trim();
    // ...
  }
}
这样就可以使用持有不同数据的对象,但通过共同的字段来区分它们。
这在 TypeScript 是很常见的;然而,根据个人的喜好,你可能想对上例中的 kind 和 payload 进行解构。
就像下面这样:
type Action =
  | { kind: 'NumberContents'; payload: number }
  | { kind: 'StringContents'; payload: string };
function processAction(action: Action) {
  const { kind, payload } = action;
  if (kind === 'NumberContents') {
    let num = payload * 2;
    // ...
  } else if (kind === 'StringContents') {
    const str = payload.trim();
    // ...
  }
}
此前,TypeScript 会报错 - 当 kind 和 payload 是由同一个对象解构为变量时,它们会被独立对待。
在 TypeScript 4.6 中可以正常工作!
当解构独立的属性为 const 声明,或当解构参数到变量且没有重新赋值时,TypeScript 会检查被解构的类型是否为可辨识联合。
如果是的话,TypeScript 就能够根据类型检查来细化变量的类型。
因此上例中,通过检查 kind 的类型可以细化 payload 的类型。
更多详情请查看 PR。
改进的递归深度检查
TypeScript 要面对一些有趣的挑战,因为它是构建在结构化类型系统之上,同时又支持了泛型。
在结构化类型系统中,对象类型的兼容性是由对象包含的成员决定的。
interface Source {
  prop: string;
}
interface Target {
  prop: number;
}
function check(source: Source, target: Target) {
  target = source;
  // error!
  // Type 'Source' is not assignable to type 'Target'.
  //   Types of property 'prop' are incompatible.
  //     Type 'string' is not assignable to type 'number'.
}
Source 与 Target 的兼容性取决于它们的属性是否可以执行赋值操作。
此例中是指 prop 属性。
当引入了泛型后,有一些难题需要解决。
例如,下例中的 Source<string> 是否可以赋值给 Target<number>?
interface Source<T> {
  prop: Source<Source<T>>;
}
interface Target<T> {
  prop: Target<Target<T>>;
}
function check(source: Source<string>, target: Target<number>) {
  target = source;
}
要想回答这个问题,TypeScript 需要检查 prop 的类型是否兼容。
这又要回答另一个问题:Source<Source<string>> 是否能够赋值给 Target<Target<number>>?
要想回答这个问题,TypeScript 需要检查 prop 的类型是否与那些类型兼容,
结果就是还要检查 Source<Source<Source<string>>> 是否能够赋值给 Target<Target<Target<number>>>?
继续发展下去,就会注意到类型会进行无限展开。
TypeScript 使用了启发式的算法 - 当一个类型达到特定的检查深度时,它表现出了将会进行无限展开, 那么就认为它可能是兼容的。 通常情况下这是没问题的,但是也可能出现漏报的情况。
interface Foo<T> {
  prop: T;
}
declare let x: Foo<Foo<Foo<Foo<Foo<Foo<string>>>>>>;
declare let y: Foo<Foo<Foo<Foo<Foo<string>>>>>;
x = y;
通过人眼观察我们知道上例中的 x 和 y 是不兼容的。
虽然类型的嵌套层次很深,但人家就是这样声明的。
启发式算法要处理的是在探测类型过程中生成的深层次嵌套类型,而非程序员明确手写出的类型。
TypeScript 4.6 现在能够区分出这类情况,并且对上例进行正确的错误提示。
此外,由于不再担心会对明确书写的类型进行误报,
TypeScript 能够更容易地判断类型的无限展开,
并且降低了类型兼容性检查的成本。
因此,像 DefinitelyTyped 上的 redux-immutable 、 react-lazylog 和 yup
代码库,对它们的类型检查时间降低了 50%。
你可能已经体验过这个改动了,因为它被挑选合并到了 TypeScript 4.5.3 中, 但它仍然是 TypeScript 4.6 中值得关注的一个特性。 更多详情请阅读 PR。
索引访问类型推断改进
TypeScript 现在能够正确地推断通过索引访问到另一个映射对象类型的类型。
interface TypeMap {
  number: number;
  string: string;
  boolean: boolean;
}
type UnionRecord<P extends keyof TypeMap> = {
  [K in P]: {
    kind: K;
    v: TypeMap[K];
    f: (p: TypeMap[K]) => void;
  };
}[P];
function processRecord<K extends keyof TypeMap>(record: UnionRecord<K>) {
  record.f(record.v);
}
// 这个调用之前是有问题的,但现在没有问题
processRecord({
  kind: 'string',
  v: 'hello!',
  // 'val' 之前会隐式地获得类型 'string | number | boolean',
  // 但现在会正确地推断为类型 'string'。
  f: val => {
    console.log(val.toUpperCase());
  },
});
该模式已经被支持了并允许 TypeScript 判断 record.f(record.v) 调用是合理的,
但是在以前,processRecord 调用中对 val 的类型推断并不好。
TypeScript 4.6 改进了这个情况,因此在启用 processRecord 时不再需要使用类型断言。
更多详情请阅读 PR。
对因变参数的控制流分析
函数签名可以声明为剩余参数且其类型可以为可辨识联合元组类型。
function func(...args: ['str', string] | ['num', number]) {
  // ...
}
这意味着 func 的实际参数完全依赖于第一个实际参数。
若第一个参数为字符串 "str" 时,则第二个参数为 string 类型。
若第一个参数为字符串 "num" 时,则第二个参数为 number 类型。
像这样 TypeScript 是由签名来推断函数类型时,TypeScript 能够根据依赖的参数来细化类型。
type Func = (...args: ['a', number] | ['b', string]) => void;
const f1: Func = (kind, payload) => {
  if (kind === 'a') {
    payload.toFixed(); // 'payload' narrowed to 'number'
  }
  if (kind === 'b') {
    payload.toUpperCase(); // 'payload' narrowed to 'string'
  }
};
f1('a', 42);
f1('b', 'hello');
更多详情请阅读 PR。
--target es2022
TypeScript 的 --target 编译选项现在支持使用 es2022。
这意味着像类字段这样的特性能够稳定地在输出结果中保留。
这也意味着像 Arrays 的上 at() 和 Object.hasOwn 方法
或者 new Error 时的 cause 选项
可以通过设置新的 --target 或者 --lib es2022 来使用。
感谢 Kagami Sascha Rosylight (saschanaz) 的实现。
删除 react-jsx 中不必要的参数
在以前,当使用 --jsx react-jsx 来编译如下的代码时
export const el = <div>foo</div>;
TypeScript 会生成如下的 JavaScript 代码:
import { jsx as _jsx } from 'react/jsx-runtime';
export const el = _jsx('div', { children: 'foo' }, void 0);
末尾的 void 0 参数是没用的,删掉它会减小打包的体积。
感谢 https://github.com/a-tarasyuk 的 PR,TypeScript 4.6 会删除 void 0 参数。
JSDoc 命名建议
在 JSDoc 里,你可以用 @param 标签来文档化参数。
/**
 * @param x The first operand
 * @param y The second operand
 */
function add(x, y) {
  return x + y;
}
但是,如果这些注释已经过时了会发生什么?就比如,我们将 x 和 y 重命名为 a 和 b?
/**
 * @param x {number} The first operand
 * @param y {number} The second operand
 */
function add(a, b) {
  return a + b;
}
在之前 TypeScript 仅会在对 JavaScript 文件执行类型检查时报告这个问题 - 通过
使用 checkJs 选项,或者在文件顶端添加 // @ts-check 注释。
现在,你能够在编译器中的 TypeScript 文件上看到类似的提示! TypeScript 现在会给出建议,如果函数签名中的参数名与 JSDoc 中的参数名不一致。

该改动是由 Alexander Tarasyuk 提供的!
JavaScript 中更多的语法和绑定错误提示
TypeScript 将更多的语法和绑定错误检查应用到了 JavaScript 文件上。
如果你在 Visual Studio 或 Visual Studio Code 这样的编辑器中打开 JavaScript 文件时就会看到这些新的错误提示,
或者当你使用 TypeScript 编译器来处理 JavaScript 文件时 - 即便你没有打开 checkJs 或者添加 // @ts-check 注释。
做为例子,如果在 JavaScript 文件中的同一个作用域中有两个同名的 const 声明,
那么 TypeScript 会报告一个错误。
const foo = 1234;
//    ~~~
// error: Cannot redeclare block-scoped variable 'foo'.
// ...
const foo = 5678;
//    ~~~
// error: Cannot redeclare block-scoped variable 'foo'.
另外一个例子,TypeScript 会报告修饰符是否被正确地使用了。
function container() {
  export function foo() {
    //  ~~~~~~
    // error: Modifiers cannot appear here.
  }
}
这些检查可以通过在文件顶端添加 // @ts-nocheck 注释来禁用,
但是我们很想听听在大家的 JavaScript 工作流中使用该特性的反馈。
你可以在 Visual Studio Code 安装 TypeScript 和 JavaScript Nightly 扩展 来提前体验,
并阅读 PR1 和 PR1。
TypeScript Trace 分析器
有人偶尔会遇到创建和比较类型时很耗时的情况。
TypeScript 提供了一个 --generateTrace 选项来帮助识别耗时的类型,
或者帮助诊断 TypeScript 编译器中的问题。
虽说由 --generateTrace 生成的信息是非常有帮助的(尤其是在 TypeScript 4.6 的改进后),
但是阅读这些 trace 信息是比较难的。
近期,我们发布了 @typescript/analyze-trace 工具来帮助阅读这些信息。
虽说我们不认为每个人都需要使用 analyze-trace,但是我们认为它会为遇到了 TypeScript 构建性能问题的团队提供帮助。
更多详情请查看 repo。