- 文档
- 教程
【免费下载链接】TypeScript
TypeScript 使用手册(中文版)翻译。http://www.typescriptlang.org
本文基于仓库中 TypeScript 3.5 版本发布说明 整理而成。TypeScript 3.5 是一版以"性能 + 类型安全 + 类型推断能力"为核心升级的里程碑版本:它在 3.4 基础上大幅优化了类型检查与
--incremental增量构建的速度,新增了内置Omit辅助类型,强化了联合类型下的多余属性检查与判别式联合的智能分解推断,并带来了泛型构造函数的高阶类型推断。读完本文,你将掌握这些特性的具体用法、底层原理与可能引发的破坏性变更,并能在自己的项目中直接落地使用。
一、改进速度:类型检查与增量构建的双重优化
TypeScript 3.5 针对 3.4 版本引入了多项性能优化,重点覆盖两个方向:常规类型检查路径,以及--incremental增量构建模式。
1.1 类型检查速度提升
3.5 对 3.4 的某些内部处理逻辑进行了优化,使类型检查整体更高效。这一收益在类型检查驱动的操作(例如代码补全列表、hover 提示、重命名等编辑器场景)上尤其显著——这些操作往往需要在极短时间内反复进行局部类型检查,优化后响应更跟手。
1.2 改进--incremental:重新构建提速 68%
--incremental构建模式由 TypeScript 3.4 引入(见 TypeScript 3.4 版本说明):开启该标志后,编译器会把上一次编译的项目图信息保存到.tsbuildinfo文件中,下次构建时据此只重做成本最低的检查与生成工作。
3.5 的核心改进在于扩大了缓存范围——除了类型信息本身,编译器还缓存了:
- 编译器设置(compiler options 的计算结果);
- 寻找文件的原因(文件解析的触发逻辑);
- 文件在哪里被找到(模块解析结果与路径)。
换言之,3.5 把"为什么要找、找到了什么、在哪里找到"这三个阶段的中间状态全部沉淀下来,避免后续构建重复计算。官方在引入该特性的 pull request 中报告:重新构建花费的时间比 TypeScript 3.4 减少了约 68%(对应--incremental改进的 PR 为缓存tsconfig.json计算,模块解析缓存见缓存模块解析的 PR)。
注:
.tsbuildinfo文件是纯构建缓存,可以安全删除,不会影响运行时行为;它仅用于加速编译。更早版本(3.6)在此基础上进一步公开了createIncrementalProgram、createIncrementalCompilerHost与readBuilderProgram等 API,供 Gulp、Webpack 等第三方构建工具操作增量构建,可参考 TypeScript 3.6 版本说明。
一个典型的--incremental配置如下(源自 TypeScript 3.4 版本说明):
// tsconfig.json { "compilerOptions": { "incremental": true, "outDir": "./lib" }, "include": ["./src"] }开启后,tsc默认在输出目录(./lib)下生成/复用.tsbuildinfo;也可以用--tsBuildInfoFile标志自定义缓存文件的位置与名称。
二、Omit辅助类型:从类型中剔除指定属性
TypeScript 3.5 在标准库中新增了Omit<Type, Keys>辅助类型,用于创建从原始类型中移除了某些属性的新类型,这是日常类型编程中使用频率极高的工具类型。
2.1 基本用法
type Person = { name: string; age: number; location: string; }; type QuantumPerson = Omit<Person, 'location'>; // 相当于 type QuantumPerson = { name: string; age: number; };使用Omit辅助类型,我们可以快速复制Person中除了location之外的所有属性,而无需手动重写一遍完整的字段列表。
Omit<Type, Keys>的语义可以概括为:从类型Type中获取所有属性,再从中剔除Keys指定的属性后构造一个新类型。它在仓库的 工具类型参考文档 中有完整收录,典型示例如下:
interface Todo { title: string; description: string; completed: boolean; } type TodoPreview = Omit<Todo, 'description'>; const todo: TodoPreview = { title: 'Clean room', completed: false, };2.2 破坏性变更:lib.d.ts全局声明了Omit
由于 3.5 将Omit内建进lib.d.ts(标准库声明文件),如果你在项目里全局定义过自己的Omit,升级后会触发以下编译错误:
Duplicate identifier 'Omit'.对此,仓库的 TypeScript 3.5 破坏性变更文档 给出了两个变通方案:
- 删除重复定义,直接使用
lib.d.ts提供的Omit; - 从模块中导出你自己的定义,避免与全局冲突;现有使用方可通过
import显式引用项目旧有的Omit类型。
值得一提的是,早在 TypeScript 2.8 版本说明中,官方就曾明确表示暂不新增Omit<T, K>,因为它可以很容易地用Pick<T, Exclude<keyof T, K>>表示;3.5 将其纳入标准库,正是对这一长期诉求的正式回应。
三、改进联合类型中的多余属性检查(Excess Property Checks)
在 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! };name的类型是boolean,它在Point与Label中都不匹配,但在 3.4 及以前,无判别(discriminant)的联合类型不会对其成员执行任何多余属性检查,于是这个类型错误的name属性溜了进来。
3.1 3.5 的新规则
在 TypeScript 3.5 中,类型检查器至少会验证所有提供的属性属于联合类型中的某个成员,且类型恰当。因此上面的例子会正确报错。
同时,规则保留了一定的灵活性:只要属性类型对某个成员有效,仍允许部分重叠。例如:
const pl: Point | Label = { x: 0, y: 0, name: 'origin', // okay };这里name: 'origin'的类型是string,恰好匹配Label成员的name: string,因此完全合法。
四、--allowUmdGlobalAccess标志:模块内也可访问 UMD 全局声明
TypeScript 3.5 新增了--allowUmdGlobalAccess编译选项。在此之前,一个以 UMD(Universal Module Definition)格式声明的全局变量,在非模块文件中可以直接当作全局使用,但在模块内部却无法引用其全局形态;开启该标志后,你可以从任何位置(包括模块)引用全局的 UMD 声明。
UMD 声明的典型形态是在.d.ts文件中使用export as namespace:
export as namespace foo;这个模式增加了"混合和匹配"第三方库的灵活性——那些库声明的全局变量,从此总是可以被使用,甚至可以安全地在模块内部被引用,而不再需要额外编写import或declare global之类的桥接代码。
使用方式:在tsconfig.json中开启,或通过命令行传入:
{ "compilerOptions": { "allowUmdGlobalAccess": true } }tsc --allowUmdGlobalAccess适用前提:该标志只对以 UMD 方式(通过
export as namespace)声明的库生效;普通全局declare var不受影响。
五、更智能的联合类型检查:判别属性的自动分解推断
这是 3.5 在类型推断能力上的一次重要跃迁。先看 3.4 及之前会报错的场景:
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;这个赋值在过去是非法的。原因是:S无法被单独分配给{ done: false, value: number },也无法被单独分配给{ done: true, value: number }——因为S中的done是宽泛的boolean,而T的每个成员都要求明确的字面量类型true或false。
这种"逐个成员检查"的方式正是问题所在:TypeScript 不会先把各个属性合并起来整体判断S是否能赋值给T。如果不做这种整体合并,一些糟糕的代码可能会被放行:
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 - 幸运的是,TypeScript 在这里会提示错误! doSomething({ kind: 'foo', value: 123, });value: 123与Foo.value: string冲突,这里必须报错。然而,对于最初的例子,逐个成员检查又显得过于严格——如果我们能弄清S的任何可能值的精确类型,实际上可以看到它与T中的类型完全匹配。
5.1 3.5 的解决思路:判别属性驱动分解
在 TypeScript 3.5 中,当把S赋值给具有判别属性(discriminant property)的类型T时,类型检查器会将S分解为每个可能成员类型的联合再逐一比对。具体来说:
boolean本质上是字面量true与false的联合;- 因此
S会被视为{ done: false, value: number }与{ done: true, value: number }的联合; - 这两个成员恰好与
T的两个分支完全吻合,赋值合法。
这一机制让"宽泛属性 + 判别联合"的代码在 3.5 中得以正确通过类型检查,同时仍不会放行像kind: 'foo', value: 123这类真正类型错误的写法,做到了安全性与实用性的平衡。
六、泛型构造函数的高阶类型推断
TypeScript 3.4 已经改进了对返回函数的泛型函数的推断。以组合函数compose为例:
function compose<T, U, V>(f: (x: T) => U, g: (y: U) => V): (x: T) => V { return x => g(f(x)); }把另外两个泛型函数作为参数传入:
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);3.4 的推断允许newFn保持泛型,其类型为<T>(x: T) => Box<T[]>,而不是旧版本推断出的相对无用的具体类型(如(x: {}) => Box<{}[]>)。
TypeScript 3.5 将这一行为推广到了构造函数(class constructor):
class Box<T> { kind: 'box'; value: T; constructor(value: T) { this.value = value; } } class Bag<U> { kind: 'bag'; value: U; constructor(value: U) { this.value = value; } } function composeCtor<T, U, V>( F: new (x: T) => U, G: new (y: U) => V ): (x: T) => V { return x => new G(new F(x)); } let f = composeCtor(Box, Bag); // 拥有类型 '<T>(x: T) => Bag<Box<T>>' let a = f(1024); // 拥有类型 'Bag<Box<number>>'组合两个泛型类Box与Bag后,f仍是泛型函数,且调用时能精确推导出Bag<Box<number>>这样的嵌套具体类型。
6.1 对 React 类组件等高阶场景的意义
除了上述组合模式,这种对泛型构造函数的新推断还意味着:在 React 等 UI 库中,对类组件进行操作的高阶函数(HOC)可以更正确地对泛型类组件进行操作。示例如下:
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>> {} // 类型为 'new <T>(props: NestedProps<T>) => Component<NestedProps<T>>' const GenericComponent2 = myHoc(GenericComponent);GenericComponent是泛型类组件,经过myHoc包装后得到的GenericComponent2仍然保留泛型参数T,而不是被推断成固定的具体组件类型——这使得 HOC 包装后的组件在 JSX 中使用时依旧具备完整的类型推导能力。
七、升级注意与延伸阅读
- 破坏性变更:由于
lib.d.ts内建Omit,请检查项目中是否全局定义过同名类型,参考 TypeScript 3.5 破坏性变更 处理重复标识符问题。 - 版本脉络:
--incremental的基础机制见 TypeScript 3.4 版本说明;3.5 之后对增量构建 API、.tsbuildinfo体积的持续优化可参考 TypeScript 3.6、TypeScript 4.3、TypeScript 4.8 等版本说明。 - 工具类型速查:
Omit、Pick、Exclude、Extract、NonNullable、Parameters、ReturnType等内置工具类型的完整用法与示例,见 工具类型参考。 - 新增功能索引:完整的新版本特性清单见 版本发布说明目录。
- 文档
- 教程
【免费下载链接】TypeScript
TypeScript 使用手册(中文版)翻译。http://www.typescriptlang.org
相关推荐
TypeScript 3.8 新特性全解析:类型导入、私有字段、顶层 await 与增量检查
TypeScript 3.8 新特性全解析:类型导入、私有字段、顶层 await 与增量检查 导读 本文基于 TypeScript 使用手册(中文版)的 3.8
文档教程linux-tutorial 之命令行的艺术:Linux 命令行高效使用实战指南
linux tutorial 之命令行的艺术:Linux 命令行高效使用实战指南 本篇指南以《命令行的艺术》(The Art of Command Line)为
文档教程TypeScript 2.2 新特性详解:混合类、`object` 类型与更严格的空值检查
TypeScript 2.2 新特性详解:混合类、 object 类型与更严格的空值检查 本文基于 TypeScript 使用手册(中文版)的 TypeScri
文档教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考