☰
TypeScript 3.5 版本新特性全解析:`Omit` 辅助类型、智能联合类型检查与增量构建提速
2026/9/29 2:39:22 网站建设 项目流程
  • 文档
  • 教程

【免费下载链接】TypeScript

TypeScript 使用手册(中文版)翻译。http://www.typescriptlang.org

项目地址:https://gitcode.com/gh_mirrors/typ/TypeScript
点击查看免费下载

本文基于仓库中 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 破坏性变更文档 给出了两个变通方案:

  1. 删除重复定义,直接使用lib.d.ts提供的Omit;
  2. 从模块中导出你自己的定义,避免与全局冲突;现有使用方可通过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

项目地址:https://gitcode.com/gh_mirrors/typ/TypeScript
点击查看免费下载
上一篇:mini-swe-agent 模型工具函数解析:get_model / get_model_name / get_model_class 的选取逻辑与全局成本控制
下一篇:Go 夜读:Go 开发者 Vim 环境配置全解析(.vimrc 完整方案)

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询