ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

TypeScript 1.4 破坏性变更全解析:最佳通用类型、严格泛型推断与类严格模式

TypeScript 1.4 破坏性变更全解析:最佳通用类型、严格泛型推断与类严格模式 文档教程【免费下载链接】TypeScriptTypeScript 使用手册中文版翻译。http://www.typescriptlang.org项目地址https://gitcode.com/gh_mirrors/typ/TypeScript点击查看免费下载本指南基于 TypeScript 使用手册中文版中的 1.4 版本破坏性变更文档 编写系统梳理 TypeScript 1.4 引入的 5 类破坏性变更最佳通用类型候选的选择、泛型接口与泛型剩余参数的推断收紧、带类型参数接口的重载解析变化以及类声明/类表达式按严格模式解析。读完本文你将理解每项变更背后的推断原理并掌握显式类型注解、重载签名、多类型参数等标准迁移手法从而安全地将旧代码升级到 TypeScript 1.4 及更高版本。背景为什么 1.4 会带来破坏性变更TypeScript 1.4 的核心新特性是联合类型Union Types。联合类型允许一个值的类型是多种类型之一例如string[] | string | (() string)同时引入了类型守卫、更好的类型推断等能力详见 1.4 版本发布说明。联合类型带来更丰富类型表达的同时也让编译器在多个候选类型面前有了更多需要抉择的场景。为了消除此前宽松、甚至出乎意料的推断行为TypeScript 1.4 对类型推断的规则进行了收紧。本页zh/breaking-changes/typescript-1.4.md记录的即是这批收紧后会导致既有代码编译行为发生变化的改动。完整的破坏性改动 issue 清单可参照官方里程碑筛选本文则逐条给出代码级说明与迁移建议。一、多个最佳通用类型候选编译器不再盲目取第一个变更内容当推断一个由多个表达式构成的数组或其它需要最佳通用类型的场景时如果存在多个同样有效的最佳通用类型候选旧版编译器会直接采用第一个候选而 TypeScript 1.4 起编译器会依据其具体实现自行做出选择。这意味着推断结果可能从一个候选切换到另一个候选从而与旧代码的预期不一致。以原文档中的例子为例var a: { x: number; y?: number }; var b: { x: number; z?: number }; // 之前 { x: number; z?: number; }[] // 现在 { x: number; y?: number; }[] var bs [b, a];a与b拥有共享的必需属性x: number以及一组互斥的可选属性y?与z?。对于[b, a]最佳通用类型既可以是{ x: number; y?: number }也可以是{ x: number; z?: number }二者都兼容全部元素。旧编译器选第一个候选即b的类型新编译器则可能选择另一个导致bs的元素类型在不同版本间不一致。原文档明确指出这种情况会在多种形态下发生一组共享的必需属性 一组互斥的可选或其它属性空类型兼容的签名类型包括泛型与非泛型签名当类型参数上应用了any时。原理补充最佳通用类型算法从 类型推论参考手册 可知当需要从几个表达式中推断类型时编译器会使用这些表达式的类型来推断最合适的通用类型通用类型算法会考虑所有候选类型并给出一个兼容所有候选类型的类型。由于最终通用类型取自候选类型候选类型共享相同的通用类型、却没有一个能作为所有候选类型的类型时就需要开发者显式指出类型。这与本文的破坏性变更同源——区别在于1.4 之前存在多个等价候选时编译器随意取第一个1.4 之后编译器按实现选择。迁移建议推荐做法使用类型注解明确指定你要使用的类型让代码不受编译器候选选择逻辑的影响var bs: { x: number; y?: number; z?: number }[] [b, a];显式写出联合后的完整对象类型既稳定了推断结果也提升了可读性。二、泛型接口与泛型调用混合类型参数从放行变为报错变更内容在旧版本中对同一泛型类型参数的多个位置传入不同但兼容比如都能落入空对象类型{}的类型编译器不会报错1.4 起这成为编译错误即使添加了约束也不行declare function fooT(x: T, y: T): T; var r foo(1, ); // r used to be {}, now this is an error这里T同时出现在x与y两个参数位置编译器需要为T找一个同时兼容number与string的类型。1.4 之前会宽松地推断为{}1.4 之后由于number与string之间不存在最佳的通用类型直接报错。添加约束同样不例外。即使把T约束为某个基类只要两个实参在约束下仍然无法统一为一个类型就会出错interface Animal { x; } interface Giraffe extends Animal { y; } interface Elephant extends Animal { z; } function fT extends Animal(x: T, y: T): T { return undefined; } var g: Giraffe; var e: Elephant; f(g, e); // Error: Giraffe 与 Elephant 之间没有最佳通用类型从 泛型手册 可知类型推断通常依赖传入实参来为类型变量确定具体类型而当多个实参对同一个T提出互斥要求时推断便无解。这也是 1.4 发布说明中严格的泛型一节的体现——1.4 发布说明 明确指出此前equal(42, hello)这类代码编译不会报错出乎意料之后则会报在 string 和 number 之间没有最佳的基本类型。迁移建议如果这种类型不匹配是有意为之请显式指定类型参数按需选择语义var r foo{}(1, ); // 模拟 1.0 的宽松行为 var r foostring | number(1, ); // 最实用显式给出联合类型 var r fooany(1, ); // 最简单放弃类型检查 fAnimal(g, e); // 将 T 显式提升为公共基类或者重写函数定义让两个参数分别拥有独立的类型参数从而声明类型不必匹配这一意图declare function fooT, U(x: T, y: U): T | U; function fT extends Animal, U extends Animal(x: T, y: U): T | U { return undefined; }注意返回值也随之从单一的T变为联合类型T | U这与 1.4 引入联合类型的语义是自洽的。三、泛型剩余参数不再允许混杂参数类型变更内容泛型剩余参数同样收紧了推断规则。旧版中用不同实参类型调用泛型剩余参数函数会被推断为{}[]并放行1.4 起直接报错function makeArrayT(...items: T[]): T[] { return items; } var r makeArray(1, ); // used to return {}[], now an errornew Array(...)的调用方式同理new Array(1, ); // 同样的错误number 与 string 之间没有最佳通用类型从 函数手册的剩余参数章节 可以理解其背景剩余参数会被编译器当作个数不限的可选参数并收集成一个数组。当一个泛型剩余参数T[]接收到的实参类型混杂如number与string时编译器需要为T找到一个能覆盖全部元素的类型而 1.4 之前会退化为{}之后则拒绝猜测。迁移建议如果希望保留 1.0 的宽松行为可以声明向后兼容的重载签名——把宽松版本作为公开签名把严格实现作为实现签名function makeArrayT(...items: T[]): T[]; function makeArray(...items: {}[]): {}[]; function makeArrayT(...items: T[]): T[] { return items; }这样对外表现为接受任意{}元素而实际实现仍保持泛型。若更希望表达元素类型允许不同则应仿照第二节的做法改用多个类型参数例如function makeArrayT, U(...items: (T | U)[]): (T | U)[]。四、带类型参数接口的重载解析推断失败不再回退到 any变更内容当一个函数类型自身带类型参数、且调用实参类型互相冲突时旧编译器会在重载解析失败后把结果回退为any1.4 起这成为错误var f10: T(x: T, b: () (a: T) void, y: T) T; var r9 f10(, () a a.foo, 1); // r9 was any, now this is an error分析这个调用第一个实参与第三个实参1都试图约束T而中间的b是一个返回(a: T) void的函数。与1之间没有最佳通用类型因此T无法唯一确定旧版本在此情况下悄悄把r9判定为any1.4 之后则如实报错——避免把推断失败静默掩盖成放弃检查这正是本次破坏性变更的初衷之一。迁移建议手动指定一个类型参数把推断歧义显式消解var r9 f10any(, () a a.foo, 1);指定any后T被固定为anyb返回的(a: any) void与调用兼容代码恢复通过检查。若希望保留更严格的类型也可以依据业务语义选择string | number等具体类型。五、类声明与类表达式按严格模式解析变更内容ECMAScript 2015 语言规范ECMA-262 6th Edition规定ClassDeclaration与ClassExpression均使用严格模式解析。TypeScript 1.4 起解析类声明或类表达式时同样施加了这些额外限制。此前在类中可用的宽松写法现在会被直接判为非法class implements {} // Invalid: implements is a reserved word in strict mode class C { foo(arguments: any) { // Invalid: arguments is not allowed as a function argument var eval 10; // Invalid: eval is not allowed as the left-hand-side expression arguments []; // Invalid: arguments object is immutable } }上述四类违例分别对应严格模式的核心限制保留字implements、interface、package、private、protected、public、static、yield等不允许作为标识符更不允许用作类名arguments与eval不允许作为变量名、参数名或赋值目标不允许重新赋值arguments对象其元素不可被改写绑定对eval/arguments的引用还会触发其它严格模式规则如禁止with、禁止删除未限定名称等。关于严格模式限制的完整列表请查阅 ECMA-262 6th Edition 的 Annex C —— The Strict Mode of ECMAScript。迁移建议凡是类内部出现的eval、arguments命名都应改用其它合法名称类名与类内部标识符需避开严格模式保留字。例如class C2 { foo(args: any) { // 用 args 替代 arguments let value 10; // 用普通变量替代 eval args[0] 1; // 直接操作数组元素而非改写 arguments 绑定 } }值得注意的是这一行为沿袭至今现代 TypeScript 中类体始终按严格模式处理因此迁移时一次性改正可避免后续版本中继续踩坑。迁移清单把旧代码升级到 TypeScript 1.4针对本页记录的破坏性变更可以归纳出四条可直接执行的迁移准则数组与字面量的最佳通用类型存在多候选时用显式类型注解固定期望结果对应第一节同一类型参数收到互斥实参类型时要么显式指定类型参数{}、string | number、any或公共基类要么把单类型参数改写为多类型参数并让返回值成为联合类型对应第二、三节带类型参数的函数类型的重载解析失败时不再静默得到any需手动指定类型参数对应第四节类声明/类表达式内避免使用严格模式禁用的标识符eval、arguments、保留字等对应第五节。这些改动的整体方向是让 TypeScript 的类型推断从宽松放行、静默回退走向严格收敛、如实报错与 1.4 引入的联合类型体系相互配合。更多相关概念可继续阅读仓库内的 泛型手册、类型推论参考、函数手册 以及 1.4 版本发布说明并可对照 破坏性变更目录 查看后续版本的类似改动。赞分享文档教程【免费下载链接】TypeScriptTypeScript 使用手册中文版翻译。http://www.typescriptlang.org项目地址https://gitcode.com/gh_mirrors/typ/TypeScript点击查看免费下载相关推荐roadmap.sh类型安全TypeScript严格模式roadmap.sh类型安全TypeScript严格模式 为什么需要严格模式 在大型开源项目如roadmap.sh中类型安全是保证代码质量和开发效率的关键文档教程知识库dokploy类型安全TypeScript严格模式与类型定义dokploy类型安全TypeScript严格模式与类型定义 引言为什么类型安全对现代部署平台至关重要 在现代云原生应用部署领域类型安全不再是一个可选项后端前端云原生DevOps容器编排运维Element Plus类型检查TypeScript严格模式与类型定义Element Plus类型检查TypeScript严格模式与类型定义 引言为什么TypeScript严格模式对企业级组件库至关重要 在现代前端开发中Ty前端UI组件上一篇Simplify死代码移除策略智能清理无用代码的艺术下一篇Beancount 测试框架如何编写高质量的单元测试和集成测试创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表