【译】TypeScript 3.5 变动内容

最近更新 VSCode 的时候,提示其内置的 Typescript 也到了 3.5.1 的版本,所以看了一下 TypeScript 3.5 的更新文档,进行了简单的翻译,具体内容以下:git

速度优化

类型检查

typescript 3.4 版本为了修复一个 bug 致使了类型检查变慢,构建时间大大增长、使用编辑器时有卡顿感。github

TypeScript 3.5 作了一些优化,目前在许多增量检查中实际上会比 TypeScript 3.3 更快。typescript

--incremental构建

TypeScript 3.4 引入了一个新的 --incremental 编译器选项。此选项将大量信息保存到.tsbuildinfo文件中,该文件可用于加速后续对 tsc 的调用。api

TypeScript 3.5 进行了一些优化,目前在几百个项目的 --build 场景中,与TypeScript 3.4相比,重建的时间能够减小68% !!浏览器

Omit 终于内置

lib.d.ts 如今内置 Omit 了,以下安全

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
复制代码

所以,以前若是自行添加过全局的Omit 会致使报错:Duplicate identifier 'Omit'.app

两种解决方法:编辑器

  1. 删除本身添加的Omit,只用lib.d.ts 提供的Omit
  2. 把本身添加的Omit 改成export 而非放到全局

优化 union 类型的多余属性检查

TypeScript有一个特性,叫作对象文本中的多余属性检查,其有助于避免 typoide

TypeScript 3.4 及以前,这一功能有一些问题,例以下面的例子不会报错函数

type Point = {
    x: number;
    y: number;
};

type Label = {
    name: string;
};

const thing: Point | Label = {
    x: 0,
    y: 0,
    name: true // uh-oh!
};
复制代码

在 TypeScript 3.5 中,类型检查器至少验证所提供的属性属于某个 union 成员,而且具备适当的类型,来保证上面的例子会报错。

目前没有发现这个优化会产生问题,但若是这项优化致使了代码检查不经过的问题,可使用这两种方法解决:

  • 向对象添加类型断言(e.g. { myProp: SomeType } as ExpectedType)
  • 向预期的类型添加索引签名,以表示预期有未指定的属性(e.g. interface ExpectedType { myProp: SomeType; [prop: string]: unknown })

--allowUmdGlobalAccess flag

allowUmdGlobalAccess标志将容许从任何地方访问 UMD 模块中定义的全局,包括从 modules 中访问。这多是不安全的,由于并非全部 UMD 模块都在模块系统中定义全局,可是在某些状况下,您可能确实须要这种能力。例如,在浏览器环境中,一个UMD模块须要在 require.js 以前加载。该模块所以只能经过全局的方式来访问。所以,若是你确实遇到了这种状况,能够用这个标志来摆脱困境

更智能的 union 类型检查

type S = { done: boolean, value: number }
type T =
    | { done: false, value: number }
    | { done: true, value: number };

declare let source: S;
declare let target: T;

target = source;

复制代码

在TypeScript 3.5以前,上述检查将会失败,由于 S 既不能赋值给 { done: false, value: number } 也不能赋值给 { done: true, value: number }。这种设计有时会避免一些 bug:

interface Foo {
    kind: "foo";
    value: string;
}

interface Bar {
    kind: "bar";
    value: number;
}

function doSomething(x: Foo | Bar) {
    if (x.kind === "foo") {
        x.value.toLowerCase();
    }
}

// uh-oh - luckily TypeScript errors here!
doSomething({
    kind: "foo",
    value: 123,
});
复制代码

但这种设计仍是比较诡异,在TypeScript 3.5中,当用T这样的区别属性分配类型时,语言会更进一步,将S这样的类型分解为每一个可能的类型的 union。在本例中,因为booleantruefalse的联合,因此S将被视为{done: false, value: number} | {done: true, value: number}

泛型构造器的高阶类型推断

TypeScript 3.4 改进了「返回函数的泛型函数」的类型推断:

function arrayify<T>(x: T): T[] {
    return [x];
}

type Box<U> = { value: U }
function boxify<U>(y: U): Box<U> {
    return { value: y };
}

let newFn = compose(arrayify, boxify);
复制代码

在 TypeScript 3.4 以前的版本,newFN将是 (x: {}) => Box<{}[]>,在 TypeScript 3.4 以后,newFn 将是 <T>(x: T) => Box<T[]>

TypeScript 3.5 将这种行为带到了构造函数上,在某些 UI 库(如React)中操做类组件的函数能够更正确地操做泛型类组件:

type ComponentClass<P> = new (props: P) => Component<P>;
declare class Component<P> {
    props: P;
    constructor(props: P);
}

declare function myHoc<P>(C: ComponentClass<P>): ComponentClass<P>;

type NestedProps<T> = { foo: number, stuff: T };

declare class GenericComponent<T> extends Component<NestedProps<T>> {
}

// type is 'new <T>(props: NestedProps<T>) => Component<NestedProps<T>>'
const GenericComponent2 = myHoc(GenericComponent);

复制代码

函数的泛型参数将被隐式约束为 unknown

之前泛型参数的隐式约束是空对象类型{}。在 TypeScript 3.5 中,没有显式约束的泛型类型参数如今隐式地约束为unknown

{}unknown 有下述不一样:

  • {} 能够被相似这样k["foo"] 访问(虽然在 --noImplicitAny 下会报错)
  • {}不能被赋值为 nullundefined,但 unknown 能够
  • {} 能够被赋值给object,但 unknown 能够

调用时,这意味着相似 T.toString() 等会报错:

function foo<T>(x: T): [T, string] {
    return [x, x.toString()]
    // 会报错:T 里面没有 toString 属性
}
// 解决方法:增长显式约束
function foo<T extends {}>(x: T): [T, string] {
    return [x, x.toString()]
}
复制代码

对泛型参数的类型推断失败时,返回的类型一样会从 {} 变成 unknown

function parse<T>(x: string): T {
    return JSON.parse(x);
}

// k has type 'unknown' - previously, it was '{}'.
const k = parse("...");

// 解决方法:
// 'k' now has type '{}'
const k = parse<{}>("...");
复制代码

{ [k: string]: unknown } 再也不是任意对象类型的有效赋值目标

TypeScript中的索引签名 { [s: string]: any }的行为很特别:它是任何对象类型的有效赋值目标。这是一个特殊的规则,由于带有索引签名的类型一般不会产生这种行为。

在以前 unknown 在这种状况下的表现与 any 相同 ,{ [s: string]: unknown } 一样是任意对象类型的有效赋值目标

let dict: { [s: string]: unknown };
// Was okay
dict = () => {};
复制代码

通常来讲,这个规则是有意义的——隐含的约束「它的全部属性都是unknown的某个子类型」对于任何对象类型都是很是正确的。然而,在TypeScript 3.5中,{[s: string]: unknown} 这个特殊规则被删掉了,缘由是上面的变动:

对泛型参数的类型推断失败时,返回的类型一样会从 {} 变成 unknown

declare function someFunc(): void;
declare function fn<T>(arg: { [k: string]: T }): void;
fn(someFunc);
复制代码

在TypeScript 3.4中,依次发生以下状况:

  • T 找不到可选类型
  • 因此 T 被视为{}
  • someFunc 不能赋值给 arg,由于没有任何特殊规则,容许给 {[k: string]:{}} 随意赋值
  • 报错

因为对泛型参数的类型推断失败时,其类型从 {} 变成 unknownarg的类型将变成{[k: string]: unknown},任何东西均可以赋值给它,所以调用将被错误地容许。因此 TypeScript 3.5 中,{ [k: string]: unknown }` 再也不是任意对象类型的有效赋值目标。

注意:普通的对象字面量不受影响:

const obj = { m: 10 }; 
// okay
const dict: { [s: string]: unknown } = obj;

复制代码

以前的 { [s: string]: unknown },根据预期行为,可使用几种替代方法:

  • { [s: string]: any }
  • { [s: string]: {} }
  • object
  • unknown
  • any

修复了经过索引修改对象属性时,类型检查过于宽松的问题

TypeScript 3.4 下,如下逻辑不会报错

type A = {
    s: string;
    n: number;
};

const a: A = { s: "", n: 0 };

function write<K extends keyof A>(arg: A, key: K, value: A[K]): void {
    // ???
    arg[key] = "hello, world";
}
// Breaks the object by putting a string where a number should be
write(a, "n", "oops");
复制代码

在TypeScript 3.5中,这个逻辑被修复,上面的示例正确地发出了一个错误。

这种错误的大多数表示相关代码中有潜在错误。若是确定没错,则能够强行使用一发类型断言。

在 ES5 环境下,Object.keys将拒绝原始值类型

ES5 环境下,若是调用 Object.keys 时传入一个非对象参数,会抛出错误,但ES2015中,若是传入的参数是原始类型, Object.keys 将返回 []

以前 TypeScript 没注意到这种状况,这会致使 ES5 环境下可能出问题,如今若是发现目标环境是 ES5,向 Object.keys 传入原始类型,会报错。

因此下述状况下,有可能须要额外添加类型断言:

function fn(arg: object | number, isArgActuallyObject: boolean) {
    if (isArgActuallyObject) {
        const k = Object.keys(arg as object);
    }
}
复制代码

由于:

函数的泛型参数将被隐式约束为 unknown

因此下面的调用也可能所以报错

declare function fn<T>(): T;

// Was okay in TypeScript 3.4, errors in 3.5 under --target ES5
Object.keys(fn());
复制代码

Editor 加强:Smart Select

TypeScript 3.5 让 vscode 支持下图的功能了:

Expand selection

Editor 加强:抽取匿名 type 为具名 type

TypeScript 3.5 让 vscode 支持下图的功能了:

Extracting part of complex type to a type alias

展望将来

咱们预计3.6将带来更好的创做和使用 generators 的体验,支持ECMAScript的 private fields proposal,以及为「支持快速增量构建和项目引用的构建工具」提供新 api。

自 3.6 起,更新频率将从每 2 个月发一版变为每 3 个月发一版。

Happy hacking!

– Daniel Rosenwasser and the TypeScript team

相关文章
相关标签/搜索