ARTICLE DETAIL

资讯详情

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

TypeScript 1.8 新特性全解析:控制流分析、模块扩充、字符串字面量类型与工具链升级

TypeScript 1.8 新特性全解析:控制流分析、模块扩充、字符串字面量类型与工具链升级 文档教程【免费下载链接】TypeScriptTypeScript 使用手册中文版翻译。http://www.typescriptlang.org项目地址https://gitcode.com/gh_mirrors/typ/TypeScript点击查看免费下载导读本文基于 TypeScript 使用手册中文版仓库的 TypeScript 1.8 版本发布说明系统梳理该版本引入的类型系统能力F-Bounded 多态、控制流分析、字符串字面量类型、基于this的类型收窄、模块与 JSX 支持改进以及编译器命令行与tsconfig.json的工具链升级。读完本文你将掌握 1.8 中每个新特性的语法形态、编译选项开关含默认值与禁用方式并能结合实际代码理解其底层行为。TypeScript 1.8 是语言与工具链双线并进的一个重要版本类型层面引入了控制流分析、字符串字面量类型、基于this的类型收窄与模块扩充工具链层面则允许.js文件直接编译、支持tsconfig.json注释、--project指向任意配置文件并让模块输出统一带上use strict。本文逐节还原官方发布说明的完整内容并结合仓库中的编译选项清单、破坏性改动记录等文档做深度佐证。类型参数约束F-Bounded 多态在 TypeScript 1.8 之前类型参数的限制extends约束不能引用同一个类型参数列表中的其他类型参数编译器会直接报错。1.8 解除了这一限制类型参数可以引用来自同一列表中的兄弟类型参数这种特性在类型系统理论中被称为F-Bounded PolymorphismF 有界多态参见有界量化理论。官方给出的示例是一个通用的属性合并函数function assignT extends U, U(target: T, source: U): T { for (let id in source) { target[id] source[id]; } return target; } let x { a: 1, b: 2, c: 3, d: 4 }; assign(x, { b: 10, d: 20 }); assign(x, { e: 0 }); // 错误这里T extends U表示T必须是U的子类型。调用assign(x, { e: 0 })时编译器推导T { a: number; b: number; c: number; d: number }、U { e: number }由于{ e: number }并不是{ a...d }的属性子集约束失败而报错——这正是参数约束可以引用兄弟参数带来的类型检查能力。泛型与约束的基础知识可进一步参考手册的泛型章节。控制流错误分析TypeScript 1.8 引入了控制流分析Control Flow Analysis来捕获开发者常见的编码错误。该能力覆盖四类检查其中两类默认开启、两类默认关闭均可通过编译选项控制详见 compiler-options.md 中--allowUnreachableCode、--allowUnusedLabels、--noImplicitReturns、--noFallthroughCasesInSwitch四行的说明检查项默认状态相关编译选项不可及代码Unreachable Code默认开启--allowUnreachableCode禁用未使用的标签Unused Labels默认开启--allowUnusedLabels禁用隐式返回Implicit Returns默认关闭--noImplicitReturns启用Case 语句贯穿Fallthrough默认关闭--noFallthroughCasesInSwitch启用不可及的代码一定无法在运行时执行的语句会被标记代码不可及错误。典型场景是无条件限制的return、throw、break或continue之后的语句function f(x) { if (x) { return true; } else { return false; } x 0; // 错误: 检测到不可及的代码. }这个特性还能捕获一个非常常见的真实 bug——在return后误加换行。由于 JavaScript 的自动分号插入ASI会在行末结束return语句下面的对象字面量变成了一个无法到达的代码块function f() { return; // 换行导致自动插入的分号 { x: string; // 错误: 检测到不可及的代码. } }未使用的标签未使用的标签同样会被标记例如声明了loop:却从未通过break loop/continue loop引用loop: while (x 0) { // 错误: 未使用的标签. x; }注意这条检查与不可及代码一样默认开启可通过--allowUnusedLabels关闭。在 1.8 破坏性改动记录 中还给出了另一个更隐蔽的例子箭头函数中的对象字面量(x) { x: x }里x:会被解析为标签而不是属性从而触发未使用标签报错——这正是该检查能拦截的无返回值分支里隐式对象返回问题。隐式返回JavaScript 中没有返回值的代码分支会隐式返回undefined。1.8 起编译器可以将这种情况标记为隐式返回但该检查默认禁用需要--noImplicitReturns启用function f(x) { // 错误: 不是所有分支都返回了值. if (x) { return false; } // 隐式返回了 undefined }Case 语句贯穿TypeScript 现在可以在switch语句中对贯穿了几个非空 case的情况报错。该检测默认关闭通过--noFallthroughCasesInSwitch启用switch (x % 2) { case 0: // 错误: switch 中出现了贯穿的 case. console.log(even); case 1: console.log(odd); break; }而空 case 的贯穿是合法的多个 case 共享同一段执行体这是常见写法不会报错switch (x % 3) { case 0: case 1: console.log(Acceptable); break; case 2: console.log(This is *two much*!); break; }关于这四类检查的启用/禁用细节破坏性改动文档还提示如果报错但你认为代码本身合理可以通过对应编译选项阻止报错例如将allowUnreachableCode设为true。React 函数组件TypeScript 1.8 开始支持 React 的函数组件Functional Components——一种可以组合其他组件的轻量级组件。使用参数解构和默认值即可轻松定义props的类型且组件参数可以被类型系统检验// 使用参数解构和默认值轻松地定义 props 的类型 const Greeter ({ name world }) divHello, {name}!/div; // 参数可以被检验 let example Greeter nameTypeScript 1.8 /;需要说明的是使用这一特性需要配合最新版的react.d.tsDefinitelyTyped 仓库维护。JSX 的完整类型规则可参考手册的 JSX 章节。简化的 Reactprops类型管理配合最新的react.d.ts1.8 大幅简化了props的类型声明不再需要显式声明ref和key也不必再extend React.Propsref和key属性会在所有组件上拥有正确的类型ref属性在无状态函数组件即函数组件上会被正确地禁用。这让 JSX 使用体验更接近原生 HTML 元素的属性检查。在模块中扩充全局或模块作用域1.8 允许用户对任何模块进行扩充Augmentation扩充的形式与过去的外部包模块声明一致例如declare module foo { }这种语法并且可以直接嵌在自己的模块内或放在另外一个顶级外部包模块中。此外TypeScript 还以declare global { }的形式提供了对全局声明的扩充使模块在必要时可以对Array这样的全局类型进行补充。模块扩充的名称解析规则与import、export声明一致扩充的模块声明合并方式与在同一个文件中声明相同。一个重要的约束是不论是模块扩充还是全局声明扩充都不能向顶级作用域添加新的项目——它们只能为已经存在的声明添加补丁。声明合并的底层机制接口合并、命名空间合并等详见声明合并文档。模块扩充示例下面map.ts声明它会在内部修改observable.ts中声明的Observable类型为其添加map方法// observable.ts export class ObservableT { // ... }// map.ts import { Observable } from ./observable; // 扩充 ./observable declare module ./observable { // 使用接口合并扩充 Observable 类的定义 interface ObservableT { mapU(proj: (el: T) U): ObservableU; } } Observable.prototype.map /*...*/;// consumer.ts import { Observable } from ./observable; import ./map; let o: Observablenumber; o.map(x x.toFixed());注意第 2.7 版本的发布说明typescript-2.7.md中map方法内部的实现现在会要求做更多类型层面的细化但 1.8 确立的在消费侧通过declare module补丁已有模块类型的模式一直沿用至今。全局作用域扩充示例相似地在模块中可以通过declare global增强全局作用域// 确保当前文件被当做一个模块. export {}; declare global { interface ArrayT { mapToNumbers(): number[]; } } Array.prototype.mapToNumbers function () { /* ... */ };注意开头的export {};——只有文件是模块时才能使用declare global。字符串字面量类型接受一个特定字符串集合作为某个值的 API 并不少见。考虑一个可以控制动画渐变inbetweening让元素在屏幕中滑动的 UI 库传统写法容易产生错误当用户不小心拼错一个合法的值时没有任何提示declare class UIElement { animate(options: AnimationOptions): void; } interface AnimationOptions { deltaX: number; deltaY: number; easing: string; // 可以是 ease-in, ease-out, ease-in-out } // 没有报错 new UIElement().animate({ deltaX: 100, deltaY: 100, easing: ease-inout });在 TypeScript 1.8 中新增了字符串字面量类型这些类型与字符串字面量的写法一致只是写在类型的位置。用户现在可以确保类型系统捕获拼写错误interface AnimationOptions { deltaX: number; deltaY: number; easing: ease-in | ease-out | ease-in-out; } // 错误: 类型 ease-inout 不能复制给类型 ease-in | ease-out | ease-in-out new UIElement().animate({ deltaX: 100, deltaY: 100, easing: ease-inout });字符串字面量类型如今已是 TypeScript 的核心能力其使用方式联合、类型别名、函数重载在手册的字面量类型章节中有系统讲解此外还有数字字面量类型与布尔字面量类型例如function rollDice(): 1 | 2 | 3 | 4 | 5 | 6。更好的联合/交叉类型接口TypeScript 1.8 优化了源类型和目标类型都是联合或交叉类型时的类型推导。举例来说当从string | string[]推导到string | T时编译器会将类型拆解为string[]和T从而把string[]推导为T。这一改进让类型守卫与泛型工具函数的组合推导更加精确type MaybeT T | void; function isDefinedT(x: MaybeT): x is T { return x ! undefined x ! null; } function isUndefinedT(x: MaybeT): x is void { return x undefined || x null; } function getOrElseT(x: MaybeT, defaultValue: T): T { return isDefined(x) ? x : defaultValue; } function test1(x: Maybestring) { let x1 getOrElse(x, Undefined); // string let x2 isDefined(x) ? x : Undefined; // string let x3 isUndefined(x) ? Undefined : x; // string } function test2(x: Maybenumber) { let x1 getOrElse(x, -1); // number let x2 isDefined(x) ? x : -1; // number let x3 isUndefined(x) ? -1 : x; // number }上例中的x is T是类型谓词type predicate即用户定义的类型守卫函数User-Defined Type Guards其完整规则见高级类型文档。使用--outFile合并 AMD 和 System 模块在 1.8 中使用--module amd或--module system的同时指定--outFile会把所有参与编译的模块合并为单个包含多个模块闭包的输出文件。每一个模块都会根据其相对于rootDir的位置计算出自己的模块名称。示例源码两个文件// 文件 src/a.ts import * as B from ./lib/b; export function createA() { return B.createB(); }// 文件 src/lib/b.ts export function createB() { return {}; }合并结果为define(lib/b, [require, exports], function (require, exports) { use strict; function createB() { return {}; } exports.createB createB; }); define(a, [require, exports, lib/b], function (require, exports, B) { use strict; function createA() { return B.createB(); } exports.createA createA; });可见输出中以模块相对rootDir的位置lib/b、a为各闭包命名。需要提醒的是编译选项文档明确只有AMD和System能和--outFile一起使用而 1.8 的破坏性改动也提到其他--module值搭配--outFile时此前会静默生成空输出文件现在会直接报错详见 typescript-1.8 破坏性改动。支持 SystemJS 使用default导入像 SystemJS 这样的模块加载器将 CommonJS 模块做了包装并暴露为defaultES6 导入项。这导致 SystemJS 与 CommonJS 两种实现因为加载器模块导出方式不同无法共享定义。设置新的编译选项--allowSyntheticDefaultImports可以指明模块加载器会进行导入的.ts或.d.ts中未指定的某种类型的默认导入项构建编译器会由此推断存在一个default导出项并且整个模块自身与它一致。此选项在 System 模块下默认开启。编译选项文档还补充了它的默认值细节module system或设置了--esModuleInterop时为默认开启且该选项仅影响类型检查、不影响代码输出。允许循环中被引用的let/const此前在循环中创建闭包引用let/const会报错TypeScript 1.8 起支持这一写法并且循环中被函数引用的let/const声明会被输出为与let/const更新语义相符的代码let list []; for (let i 0; i 5; i) { list.push(() i); } list.forEach(f console.log(f()));编译输出_loop_1将每次迭代的i作为参数传入保证每个闭包捕获各自迭代的值var list []; var _loop_1 function (i) { list.push(function () { return i; }); }; for (var i 0; i 5; i) { _loop_1(i); } list.forEach(function (f) { return console.log(f()); });运行结果是0 1 2 3 4这修正了经典 JavaScript 闭包陷阱所有闭包共享同一个i让 TypeScript 输出与 ES6 语义保持一致。改进的for..in语句检查过去for..in变量的类型被推断为any导致编译器忽略语句内一些不合法的使用。从 TypeScript 1.8 开始for..in语句中的变量隐含类型为string当一个有数字索引签名且类型为T的对象比如数组被for..in的变量索引时产生值的类型为T。var a: MyObject[]; for (var x in a) { // x 的隐含类型为 string var obj a[x]; // obj 的类型为 MyObject }由于x的类型是string而数组恰好有数字索引签名a[x]仍能正确索引出MyObject——类型信息从此不再丢失。模块输出统一加use strict对 ES6 来说模块始终以严格模式被解析但这一点过去对于非 ES6 目标在生成的代码中并没有遵循。从 TypeScript 1.8 开始输出的模块总会处于严格模式。由于多数严格模式下的错误同时也是 TypeScript 编译时的错误大多数代码并不会有可见改动但一些东西可能在运行时没有征兆地失败比如赋值给NaN现在会有运行时错误。该改动属于破坏性变更见 breaking-changes/typescript-1.8.md若想禁用此行为可在命令行传入--noImplicitUseStrict或在tsconfig.json中指定。同一版本的破坏性改动还包括从模块导出非局部名称如export { Promise }会按 ES6 规范报错需先用局部变量捕获再导出。使用--allowJs加入.js文件项目中经常存在外部非 TypeScript 编写的源文件。一种做法是把 JS 代码转换为 TS 代码但又希望把所有 JS 代码和新的 TS 代码的输出一起打包为一个文件。.js文件现在允许作为tsc的输入文件TypeScript 编译器会检查.js输入文件的语法错误并根据--target和--module选项输出对应代码输出也会和其他.ts文件放在一起.js文件的 source maps 也会像.ts文件一样被生成。编译选项文档补充说明该选项默认值为false即默认不编译.js且可配合--checkJs在.js文件中报告类型错误、配合--maxNodeModuleJsDepth控制node_modules中 JavaScript 文件的加载深度。使用--reactNamespace自定义 JSX 工厂在使用--jsx react的同时使用--reactNamespace JSX 工厂名称可以指定一个不同于默认React的 JSX 工厂。新的工厂名称会被用来调用createElement和__spread方法。import { jsxFactory } from jsxFactory; var div divHello JSX!/div;编译参数tsc --jsx react --reactNamespace jsxFactory --m commonJS结果use strict; var jsxFactory_1 require(jsxFactory); var div jsxFactory_1.jsxFactory.createElement(div, null, Hello JSX!);编译选项文档中该选项默认值为React。后来 TypeScript 又引入了更通用的--jsxFactory选项默认React.createElement但 1.8 的--reactNamespace是最早的自定义工厂方案。基于this的类型收窄TypeScript 1.8 为类和接口方法扩展了用户定义的类型收窄函数类型守卫this is T现在可以是类或接口方法的合法返回值类型标注。当在类型收窄的位置使用比如if语句时函数调用表达式的目标对象类型会被收窄为T。class FileSystemObject { isFile(): this is File { return this instanceof File; } isDirectory(): this is Directory { return this instanceof Directory; } isNetworked(): this is Networked this { return this.networked; } constructor(public path: string, private networked: boolean) {} } class File extends FileSystemObject { constructor(path: string, public content: string) { super(path, false); } } class Directory extends FileSystemObject { children: FileSystemObject[]; } interface Networked { host: string; } let fso: FileSystemObject new File(foo/bar.txt, foo); if (fso.isFile()) { fso.content; // fso 是 File } else if (fso.isDirectory()) { fso.children; // fso 是 Directory } else if (fso.isNetworked()) { fso.host; // fso 是 networked }注意isNetworked(): this is Networked this是交叉类型与this谓词的组合收窄后fso同时具备Networkedhost和this自身的成员。该机制是后续各版本基于控制流的类型收窄的基础更完整的类型守卫parameterName is Type形式的谓词函数规则参见高级类型文档以及手册 v2 的收窄Narrowing章节。官方 TypeScript NuGet 包从 TypeScript 1.8 开始官方提供 TypeScript 编译器tsc.exe和 MSBuild 整合Microsoft.TypeScript.targets与Microsoft.TypeScript.Tasks.dll的 NuGet 包。稳定版包括Microsoft.TypeScript.Compiler与Microsoft.TypeScript.MSBuild与每日 npm 包对应的每日 NuGet 包则托管在 myget.org 的TypeScript-Preview源中。该能力为 .NET 生态如 ASP.NET 项目的构建接入提供了官方途径仓库中在 MSBuild 里使用编译选项一章详细说明了 MSBuild 场景下的配置方式。tsc错误信息更美观--pretty大量单色的输出并不直观。颜色可以帮助识别信息的始末这些视觉线索在处理复杂错误信息时非常重要。通过传递--pretty命令行选项TypeScript 会给出更丰富的输出包含错误发生的上下文高亮、上下文代码片段。编译选项文档中该选项默认值为false。高亮 VS 2015 中的 JSX 代码在 TypeScript 1.8 中JSX 标签可以在 Visual Studio 2015 中被分别和高亮。通过工具→选项→环境→字体与颜色页面在VB XML颜色和字体设置中还可以进一步自定义字体和颜色。--project-p选项接受任意文件路径--project命令行选项过去只接受包含tsconfig.json文件的文件夹。考虑到不同的构建场景1.8 起允许--project指向任何兼容的 JSON 文件。比如用户可能希望为 Node 5 编译 CommonJS 的 ES2015、为浏览器编译 AMD 的 ES5少了这项限制后可以更容易地直接用tsc管理不同的构建目标无需再通过把多个tsconfig.json放在不同目录这类迂回方式。如果参数是一个目录路径行为保持不变——编译器仍会尝试在该目录下寻找名为tsconfig.json的文件。tsconfig.json的完整管理方式见工程配置文档。允许 tsconfig.json 中的注释为配置添加文档是很棒的tsconfig.json现在支持单行和多行注释{ compilerOptions: { target: ES2015, // 跑在 node v5 上, 呀! sourceMap: true // 让调试轻松一些 }, /* * 排除的文件 */ exclude: [ file.d.ts ] }在此之前 JSON 严格禁止注释这一改动让配置文件第一次具备了内联文档能力。支持输出到 IPC 驱动的文件TypeScript 1.8 允许将--outFile参数与一些特殊的文件系统对象一起使用比如命名管道pipe、设备devices等。例如在很多与 Unix 相似的系统上标准输出流可以通过文件/dev/stdout访问tsc foo.ts --outFile /dev/stdout这一特性也允许把输出交给其他命令处理比如将生成的 JavaScript 管道给格式美化工具tsc foo.ts --outFile /dev/stdout | pretty-js改进了 Visual Studio 2015 中对 tsconfig.json 的支持TypeScript 1.8 允许在任何种类的项目中使用tsconfig.json文件包括 ASP.NET v4 项目、控制台应用以及用 TypeScript 开发的 HTML 应用。同时可以添加不止一个tsconfig.json文件其中每一个都会作为项目的一部分被构建——这使得可以在不使用多个不同项目的情况下为应用的不同部分使用不同配置。当项目中添加了tsconfig.json文件时项目属性页面会被禁用所有配置的改变必须在tsconfig.json中进行。官方文档同时给出了一些限制如果添加了一个tsconfig.json文件不在其上下文中的 TypeScript 文件不会被编译Apache Cordova 应用依然有单个tsconfig.json文件的限制且该文件必须在根目录或scripts文件夹多数项目类型中都没有tsconfig.json的模板。小结TypeScript 1.8 是一个承前启后的版本它把控制流分析带入了编译器不可及代码、未使用标签、隐式返回、case 贯穿四类检查确立了字符串字面量类型、模块/全局扩充、this is T类型谓词等至今仍被广泛使用的类型能力同时在工具链层面打通了.js直接编译、AMD/System 单文件合并、自定义 JSX 工厂、tsconfig.json注释与--project灵活性。其中多数编译选项--allowJs、--allowSyntheticDefaultImports、--noImplicitReturns、--noFallthroughCasesInSwitch、--pretty、--reactNamespace等的默认值与约束都可以在编译选项清单中查证相关的行为变更则记录在1.8 破坏性改动中可作为升级或排查问题的第一手依据。赞分享文档教程【免费下载链接】TypeScriptTypeScript 使用手册中文版翻译。http://www.typescriptlang.org项目地址https://gitcode.com/gh_mirrors/typ/TypeScript点击查看免费下载相关推荐TypeSpec 字面量类型完全指南字符串、多行字符串、模板字面量与数值/布尔字面量TypeSpec 字面量类型完全指南字符串、多行字符串、模板字面量与数值/布尔字面量 在设计 API 时我们经常需要把接口结构定义成具体的字面量值——例如某编程语言编译器后端TypeScript 4.1 新特性全解析模版字面量类型、键重映射与破坏性改动TypeScript 4.1 新特性全解析模版字面量类型、键重映射与破坏性改动 TypeScript 4.1 是一次聚焦类型系统深水区的版本更新它引入了 模文档教程TypeScript 2.6 新特性详解严格函数类型检查、模板字符串缓存与诊断体验升级TypeScript 2.6 新特性详解严格函数类型检查、模板字符串缓存与诊断体验升级 TypeScript 2.6 是语言在类型安全性与开发体验上的一次重要文档教程上一篇如何快速上手 KetcherWeb 分子绘图神器的完整指南 下一篇JavaScript代码混淆终极指南使用Closure Compiler保护前端知识产权创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表