【译】TypeScript 3.5 变更内容

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

速度优化

类型检查

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

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

--incremental构建

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

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'.

两种解决方法:

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

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

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

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 支持下图的功能了:

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

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

展望未来

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

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

Happy hacking!

– Daniel Rosenwasser and the TypeScript team

转载于:https://juejin.im/post/5d0b67246fb9a07ef06f9a51

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值