文档教程【免费下载链接】TypeScriptTypeScript 使用手册中文版翻译。http://www.typescriptlang.org项目地址https://gitcode.com/gh_mirrors/typ/TypeScript点击查看免费下载本文基于 TypeScript 2.0 破坏性变更文档系统梳理 TypeScript 2.0 中会对存量代码产生编译影响的五项破坏性变更函数/类表达式捕获变量不再进行类型细化、泛型参数参与类型细化、纯 getter 存取器被推断为readonly、严格模式下块级函数声明被禁止以及TemplateStringsArray变为不可变类型。读完本文你将能够准确识别升级 TypeScript 2.0 后可能出现的编译错误、理解其背后的控制流分析原理并为每类问题套用官方推荐的修复模式。写在前面为什么需要关注破坏性变更TypeScript 2.0 是语言发展史上的一个分水岭它引入了--strictNullChecks、基于控制流的类型分析control flow analysis、never类型、可辨识联合discriminated union等能力而这些新特性的实现方式改变了类型检查器对既有代码的判定结果。也就是说某些在 TypeScript 1.x 下合法的写法在 2.0 下会变成编译错误——这就是本节文档所列举的破坏性变更breaking changes。从本仓库的目录结构可以看到官方按版本维护了两套对应文档版本发布说明新增功能与 Breaking Changes破坏性变更。升级时两者需对照阅读先看新增了哪些能力再看哪些写法因此不再合法。本文聚焦后者逐一拆解每一条变更的触发场景、报错示例与推荐修复方案。变更一函数或类表达式不再对捕获变量进行类型细化变更内容类型细化narrowing不会在函数、类和 lambda 表达式内部继续进行。即使外层作用域已经通过typeof等类型守卫把联合类型收窄一旦进入函数体被捕获的变量仍以声明时的完整联合类型参与检查。var x: number | string; if (typeof x number) { function inner(): number { return x; // Error, type of x is not narrowed, c is number | string } var y: number x; // OK, x is number }外层if分支中x已被细化为number因此var y: number x;可以正常通过但函数inner内部的return x却报错——因为编译器不知道函数会在什么时候被执行。背后的原理回调函数何时执行不可知文档给出的第二个例子更加直观var x: number | string a; if (typeof x string) { setTimeout(() console.log(x.charAt(0)), 0); } x 5;当setTimeout的回调在 0 毫秒后真正执行时外层代码早已执行了x 5此时x的实际值是number。如果编译器允许回调内部按string使用x就会产生运行时错误。TypeScript 2.0 之前的类型检查器对这类延迟执行场景过于乐观2.0 起选择放弃在函数/类/lambda 内部对捕获变量继续细化以类型安全为优先。这一点与本仓库 narrowing 手册 中反复强调的控制流分析control flow analysis理念一脉相承类型收窄只在同步、确定的控制流路径上成立而回调的执行时机对编译器而言是不可知的。推荐修复用const捕获不可变快照const x: number | string a; if (typeof x string) { setTimeout(() console.log(x.charAt(0)), 0); }将变量改为const后x在生命周期内不可再赋值编译器可以安全地认为字符串快照在回调执行时依然成立从而保留细化结果。这也从侧面印证了为什么现代 TypeScript 实践强烈推荐能用const就不用let。变更二泛型参数也会被类型细化变更内容与捕获变量不再细化相反泛型参数 T 在类型守卫如instanceof之后会参与细化从而让原本合法的赋值变成错误function gT(obj: T) { var t: T; if (obj instanceof RegExp) { t obj; // RegExp is not assignable to T } }在instanceof RegExp为真的分支里obj被细化为RegExp类型。此时把obj赋给类型仍为T的变量t编译器会检查RegExp是否可赋值给T——由于T是任意泛型参数RegExp显然不一定是其子类型于是报错RegExp is not assignable to T。这里需要理解细化发生的层级变量obj的静态类型被细化而t的声明类型T不受影响二者的赋值关系因此被破坏。这与 advanced-types 文档 中对instanceof类型守卫的定义一致instanceof右侧要求是一个构造函数左侧类型会被细化为该构造函数的实例类型。推荐修复文档给出两种思路把局部变量声明为具体类型而不是继续使用泛型参数T——让细化后的类型和变量声明类型保持一致使用类型断言显式告知编译器这里我知道自己在做什么例如t obj as any;或t obj as T;。function gT(obj: T) { if (obj instanceof RegExp) { const re: RegExp obj; // 细化后的 RegExp 赋给 RegExp合法 // 对 re 做正则相关操作…… } }变更三只有 get 没有 set 的存取器被自动推断为 readonly变更内容只声明get而未声明set的存取器accessor会被自动视为readonly属性外部对其赋值将直接报错class C { get x() { return 0; } } var c new C(); c.x 1; // Error Left-hand side is a readonly property与其他 readonly 规则的联动这一推断并非孤立存在。本仓库 release notes 2.0 的只读属性和索引签名一节系统列出了实体被隐式视为只读的多种情形纯 getter 只是其中之一其余还包括枚举类型中的枚举成员模块类型中导出的const变量import语句中声明的实体通过import * as foo from foo命名空间导入访问的实体foo.x只读。同时readonly修饰符本身也在 2.0 中扩展到属性与索引签名只读属性允许在声明处及同类构造函数中赋值其余位置一律禁止。关于readonly修饰符与存取器的配合可进一步参考 classes 手册 的readonly 修饰符与存取器两节——那里还指出一个配套约束存取器要求编译目标为 ECMAScript 5 或更高不支持降级到 ES3。interface Point { readonly x: number; readonly y: number; } class Foo { readonly a 1; readonly b: string; constructor() { this.b hello; // 构造函数内赋值是允许的 } }推荐修复如果你确实需要可读可写的属性就定义一个不对属性写值的 setterclass C { private _x 0; get x() { return this._x; } set x(value: number) { // 即使 setter 里不真正写入声明了 set 后属性就不再被推断为 readonly } }从代码生成.d.ts的角度看纯 getter 被推断为readonly是有益的使用该类型的人会在编译期就被阻止修改只读视图避免运行时静默失败。变更四严格模式下函数声明不允许出现在块block中变更内容在严格模式strict mode下把function声明直接写在if、for等块语句里原本是运行时错误从 TypeScript 2.0 起升级为编译时错误if (true) { function foo() {} } export foo;ES2015 规范规定块级函数声明在严格模式下是语法错误TypeScript 2.0 将这一规则纳入编译期检查让问题在编译阶段就暴露出来。推荐修复改用函数表达式if (true) { const foo function () {}; }把函数声明改为绑定到const的函数表达式后foo是块作用域内的变量符合严格模式语义。如果希望foo在块外可用则应当把它提升到块外声明而不是依赖块内函数声明的历史性提升行为。变更五TemplateStringsArray变为不可变类型变更内容ES2015 模板字符串tagged template总是把标签函数tag function的第一个参数以不可变类数组对象传入该对象带有一个同样不可变的raw属性。TypeScript 将这个对象的类型命名为TemplateStringsArray。在 TypeScript 2.0 之前TemplateStringsArray恰好可以赋值给Arraystring于是不少代码用更短的string[]来标注标签参数function myTemplateTag(strs: string[]) { // ... }2.0 引入readonly修饰符后TemplateStringsArray被定义为不可变类型不再可以赋值给string[]——因为它缺少push、splice等修改数组的方法。这正是 release notes 2.0 中ReadonlyArray语义的延伸可变的ArrayT可以赋值给ReadonlyArrayT反之则不行因为后者缺少前者要求的修改方法。let a: Arraynumber [0, 1, 2, 3, 4]; let b: ReadonlyArraynumber a; b[5] 5; // 错误元素是只读的 b.push(5); // 错误没有 push 方法 b.length 3; // 错误length 是只读的 a b; // 错误缺少修改数组的方法推荐修复标签函数的参数应直接使用TemplateStringsArray或者使用ReadonlyArraystringfunction myTemplateTag(strs: TemplateStringsArray) { // 现在 strs 是不可变的符合运行时真实形态 console.log(strs.raw); } function myTemplateTag2(strs: ReadonlyArraystring) { // 等价的可接受写法 }这一变更的价值在于让静态类型与运行时的真实对象形态一致标签参数在 JavaScript 中本来就是不可变的旧的string[]标注让开发者误以为可以修改它从而埋下运行时隐患。升级建议与自查清单综合上述五项变更升级到 TypeScript 2.0 前后建议按以下清单自查全局搜索函数/类/lambda 内部对外层联合类型变量的访问若依赖外层类型守卫的细化结果改为先捕获const快照检查instanceof/typeof守卫分支内对泛型参数变量的赋值必要时改为具体类型声明或类型断言搜索只有getter没有setter的类属性确认外部代码没有对其赋值有赋值需求的补上 setter检查严格模式下块级function声明统一改为const foo function () {}形式检查标签模板函数的参数标注把string[]改为TemplateStringsArray或ReadonlyArraystring打开--strict系列开关做一次全量编译--strict在后续版本中聚合了--noImplicitAny、--noImplicitThis、--alwaysStrict、--strictNullChecks、--strictFunctionTypes、--strictPropertyInitialization等选项可参见 compiler-options 文档能最大限度暴露上述问题。值得一提的是变更一与变更二看似方向相反实则统一于同一条原则类型细化必须建立在编译器可控、可推导的控制流之上。同步引入的基于控制流的类型分析详见 release notes 2.0让细化在同步路径上比以往更精确同时把不可控的延迟执行、泛型赋值等场景排除在细化范围之外——这正是 TypeScript 类型系统走向精确但不冒险的关键一步。赞分享文档教程【免费下载链接】TypeScriptTypeScript 使用手册中文版翻译。http://www.typescriptlang.org项目地址https://gitcode.com/gh_mirrors/typ/TypeScript点击查看免费下载相关推荐从 prompt_toolkit 1.0 迁移到 2.0破坏性变更全解析与升级实战指南从 prompt_toolkit 1.0 迁移到 2.0破坏性变更全解析与升级实战指南 本文以官方升级文档 docs/pages/upgrading/2.0.CLIAWS SDK for JavaScript v2 升级指南从 1.x 迁移到 2.0 的四大破坏性变更全解析AWS SDK for JavaScript v2 升级指南从 1.x 迁移到 2.0 的四大破坏性变更全解析 导读 本文基于 UPGRADING.md ht后端开发工具Falcon 4.0 升级完全指南新特性、类型注解与破坏性变更解析Falcon 4.0 升级完全指南新特性、类型注解与破坏性变更解析 本文以 Falcon 官方 4.0.0 变更日志 docs/changes/4.0.0.后端Web框架API设计上一篇Android应用语言独立设置Language Selector终极使用指南下一篇用 Chocolatey 测试包验证越界依赖Out-of-Range Dependencyhasoutofrangedependency 实战解读创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考