最近更新 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
场景中,!
Omit
终于内置
lib.d.ts
现在内置 Omit
了,如下
type Omit= Pick >;复制代码
因此,之前如果自行添加过全局的Omit
会导致报错:Duplicate identifier 'Omit'.
两种解决方法:
- 删除自己添加的
Omit
,只用lib.d.ts
提供的Omit
- 把自己添加的
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。在本例中,由于boolean
是true
和false
的联合,所以S
将被视为{done: false, value: number} | {done: true, value: number}
泛型构造器的高阶类型推断
TypeScript 3.4 改进了「返回函数的泛型函数」的类型推断:
function arrayify(x: T): T[] { return [x];}type Box = { value: U }function boxify(y: U): Box { 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= new (props: P) => Component
;declare class Component
{ props: P; constructor(props: P);}declare function myHoc
(C: ComponentClass
): ComponentClass
;type NestedProps
= { foo: number, stuff: T };declare class GenericComponent extends Component > {}// type is 'new (props: NestedProps ) => Component >'const GenericComponent2 = myHoc(GenericComponent);复制代码
函数的泛型参数将被隐式约束为 unknown
以前泛型参数的隐式约束是空对象类型{}
。在 TypeScript 3.5 中,没有显式约束的泛型类型参数现在隐式地约束为unknown
。
{}
与 unknown
有下述不同:
{}
可以被类似这样k["foo"]
访问(虽然在--noImplicitAny
下会报错){}
不能被赋值为null
和undefined
,但unknown
可以{}
可以被赋值给object
,但unknown
可以
调用时,这意味着类似 T.toString()
等会报错:
function foo(x: T): [T, string] { return [x, x.toString()] // 会报错:T 里面没有 toString 属性}// 解决方法:增加显式约束function foo (x: T): [T, string] { return [x, x.toString()]}复制代码
对泛型参数的类型推断失败时,返回的类型同样会从 {}
变成 unknown
function parse(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 okaydict = () => {};复制代码
一般来说,这个规则是有意义的——隐含的约束「它的所有属性都是unknown
的某个子类型」对于任何对象类型都是非常正确的。然而,在TypeScript 3.5中,{[s: string]: unknown} 这个特殊规则被删掉了,原因是上面的变更:
对泛型参数的类型推断失败时,返回的类型同样会从
{}
变成unknown
declare function someFunc(): void;declare function fn(arg: { [k: string]: T }): void;fn(someFunc);复制代码
在TypeScript 3.4中,依次发生如下情况:
T
找不到可选类型- 所以
T
被视为{}
someFunc
不能赋值给arg
,因为没有任何特殊规则,允许给{[k: string]:{}}
随意赋值- 报错
由于对泛型参数的类型推断失败时,其类型从 {}
变成 unknown
,arg
的类型将变成{[k: string]: unknown}
,任何东西都可以赋值给它,因此调用将被错误地允许。所以 TypeScript 3.5 中,{ [k: string]: unknown }` 不再是任意对象类型的有效赋值目标。
注意:普通的对象字面量不受影响:
const obj = { m: 10 }; // okayconst 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(arg: A, key: K, value: A[K]): void { // ??? arg[key] = "hello, world";}// Breaks the object by putting a string where a number should bewrite(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;// Was okay in TypeScript 3.4, errors in 3.5 under --target ES5Object.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