97

TypeScript 2.5~2.6 新特性一览

 6 years ago
source link: https://zhuanlan.zhihu.com/p/30760290?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

TypeScript 2.5~2.6 新特性一览

JavaScript话题下的优秀答主
TypeScript 团队周二发布了 TypeScript 2.6, 带来了包括严格函数类型和更快的 tsc --watch 在内的多个新特性. 再早一些的 TypeScript 2.5 也带来了包括包去重和保留符号链接在内的更新. (一定是) 因为 2.5 更新内容较少, 这次随 2.6 更新一起进行了翻译.

这次更新中 "严格函数类型" 的部分可能较为晦涩, TypeScript 团队在 Announcing TypeScript 2.6 一文中做了更多解释, 可以参考阅读.

本文译自 What's new in TypeScript.

TypeScript 2.6

严格函数类型

TypeScript 2.6 引入了新的类型检查选项, --strictFunctionTypes. --strictFunctionTypes 选项是 --strict 系列选项之一, 也就是说 --strict 模式下它默认是启用的. 你可以通过在命令行或 tsconfig.json 中设置 --strictFunctionTypes false来单独禁用它.

--strictFunctionTypes 启用时, 函数类型参数的检查是逆变(contravariantly)而非双变(bivariantly)的. 关于变型 (variance) 对与函数类型意义的相关背景, 请查看协变(covariance)和逆变(contravariance)是什么?.

这一更严格的检查应用于方法或构造函数声明以外的所有函数类型. 方法被专门排除在外是为了确保带泛型的类和接口(如 Array<T>)总体上仍然保持协变.

考虑下面这个 AnimalDogCat 的父类型的例子:

declare let f1: (x: Animal) => void;
declare let f2: (x: Dog) => void;
declare let f3: (x: Cat) => void;
f1 = f2;  // 启用 --strictFunctionTypes 时错误
f2 = f1;  // 正确
f2 = f3;  // 错误 

第一个赋值语句在默认的类型检查模式中是允许的, 但是在严格函数类型模式下会被标记错误. 通俗地讲, 默认模式允许这么赋值, 因为它可能是合理的, 而严格函数类型模式将它标记为错误, 因为它不能被证明合理. 任何一种模式中, 第三个赋值都是错误的, 因为它永远不合理.

用另一种方式来描述这个例子则是, 默认类型检查模式中 T 在类型 (x: T) => void双变的 (也即协变逆变), 但在严格函数类型模式中 T逆变的.

例子

interface Comparer<T> {
    compare: (a: T, b: T) => number;
}

declare let animalComparer: Comparer<Animal>;
declare let dogComparer: Comparer<Dog>;

animalComparer = dogComparer;  // 错误
dogComparer = animalComparer;  // 正确 

现在第一个赋值是错误的. 更明确地说, Comparer<T> 中的 T 因为仅在函数类型参数的位置被使用, 是逆变的.

另外, 注意尽管有的语言 (比如 C# 和 Scala) 要求变型标注 (variance annotations) (out/in+/-), 而由于 TypeScript 的结构化类型系统, 它的变型是由泛型中的类型参数的实际使用自然得出的.

注意

启用 --strictFunctionTypes 时, 如果 compare 被声明为方法, 则第一个赋值依然是被允许的. 更明确的说, Comparer<T> 中的 T 因为仅在方法参数的位置被使用所以是双变的.

interface Comparer<T> {
    compare(a: T, b: T): number;
}

declare let animalComparer: Comparer<Animal>;
declare let dogComparer: Comparer<Dog>;

animalComparer = dogComparer;  // 正确, 因为双变
dogComparer = animalComparer;  // 正确 

TypeScript 2.6 还改进了与逆变位置相关的类型推导:

function combine<T>(...funcs: ((x: T) => void)[]): (x: T) => void {
    return x => {
        for (const f of funcs) f(x);
    }
}

function animalFunc(x: Animal) {}
function dogFunc(x: Dog) {}

let combined = combine(animalFunc, dogFunc);  // (x: Dog) => void 

这上面所有 T 的推断都来自逆变的位置, 由此我们得出 T最普遍子类型. 这与从协变位置推导出的结果恰恰相反, 从协变位置我们得出的是最普遍超类型.

缓存模块中的标签模板对象

TypeScript 2.6 修复了标签字符串模板的输出, 以更好地遵循 ECMAScript 标准. 根据 ECMAScript 标准, 每一次获取模板标签的值时, 应该将同一个模板字符串数组对象 (同一个 TemplateStringArray) 作为第一个参数传递. 在 TypeScript 2.6 之前, 每一次生成的都是全新的模板对象. 虽然字符串的内容是一样的, 这样的输出会影响通过识别字符串来实现缓存失效的库, 比如 lit-html.

例子

export function id(x: TemplateStringsArray) {
    return x;
}

export function templateObjectFactory() {
    return id`hello world`;
}

let result = templateObjectFactory() === templateObjectFactory(); // TS 2.6 为 true 

编译后的代码:

"use strict";
var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
    if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
    return cooked;
};

function id(x) {
    return x;
}

var _a;
function templateObjectFactory() {
    return id(_a || (_a = __makeTemplateObject(["hello world"], ["hello world"])));
}

var result = templateObjectFactory() === templateObjectFactory(); 
注意: 这一改变引入了新的工具函数, __makeTemplateObject; 如果你在搭配使用 --importHelperstslib, 需要更新到 1.8 或更高版本.

本地化的命令行诊断消息

TypeScript 2.6 npm 包加入了 13 种语言的诊断消息本地化版本. 命令行中本地化消息会在使用 --locale 选项时显示.

例子

俄语显示的错误消息:

c:\ts>tsc --v
Version 2.6.1

c:\ts>tsc --locale ru --pretty c:\test\a.ts

../test/a.ts(1,5): error TS2322: Тип ""string"" не может быть назначен для типа "number".

1 var x: number = "string";
      ~ 

中文显示的帮助信息:

PS C:\ts> tsc --v
Version 2.6.1

PS C:\ts> tsc --locale zh-cn
版本 2.6.1
语法: tsc [选项] [文件 ...]

示例: tsc hello.ts
    tsc --outFile file.js file.ts
    tsc @args.txt

选项:
 -h, --help                    打印此消息。
 --all                         显示所有编译器选项。
 -v, --version                 打印编译器的版本。
 --init                        初始化 TypeScript 项目并创建 tsconfig.json 文件。
 -p 文件或目录, --project 文件或目录     编译给定了其配置文件路径或带 "tsconfig.json" 的文件夹路径的项目。
 --pretty                      使用颜色和上下文风格化错误和消息(实验)。
 -w, --watch                   监视输入文件。
 -t 版本, --target 版本            指定 ECMAScript 目标版本: "ES3"(默认)、"ES5"、"ES2015"、"ES2016"、"ES2017" 或 "ESNEXT"。
 -m 种类, --module 种类            指定模块代码生成: "none"、"commonjs"、"amd"、"system"、"umd"、"es2015"或 "ESNext"。
 --lib                         指定要在编译中包括的库文件:
                                 'es5' 'es6' 'es2015' 'es7' 'es2016' 'es2017' 'esnext' 'dom' 'dom.iterable' 'webworker' 'scripthost' 'es2015.core' 'es2015.collection' 'es2015.generator' 'es2015.iterable' 'es2015.promise' 'es2015.proxy' 'es2015.reflect' 'es2015.symbol' 'es2015.symbol.wellknown' 'es2016.array.include' 'es2017.object' 'es2017.sharedmemory' 'es2017.string' 'es2017.intl' 'esnext.asynciterable'
 --allowJs                     允许编译 JavaScript 文件。
 --jsx 种类                      指定 JSX 代码生成: "preserve"、"react-native" 或 "react"。 -d, --declaration             生成相应的 ".d.ts" 文件。
 --sourceMap                   生成相应的 ".map" 文件。
 --outFile 文件                  连接输出并将其发出到单个文件。
 --outDir 目录                   将输出结构重定向到目录。
 --removeComments              请勿将注释发出到输出。
 --noEmit                      请勿发出输出。
 --strict                      启用所有严格类型检查选项。
 --noImplicitAny               对具有隐式 "any" 类型的表达式和声明引发错误。
 --strictNullChecks            启用严格的 NULL 检查。
 --strictFunctionTypes         对函数类型启用严格检查。
 --noImplicitThis              在带隐式“any" 类型的 "this" 表达式上引发错误。
 --alwaysStrict                以严格模式进行分析,并为每个源文件发出 "use strict" 指令。
 --noUnusedLocals              报告未使用的局部变量上的错误。
 --noUnusedParameters          报告未使用的参数上的错误。
 --noImplicitReturns           在函数中的所有代码路径并非都返回值时报告错误。
 --noFallthroughCasesInSwitch  报告 switch 语句中遇到 fallthrough 情况的错误。
 --types                       要包含在编译中类型声明文件。
 @<文件>                         从文件插入命令行选项和文件。 

通过 '// @ts-ignore' 注释隐藏 .ts 文件中的错误

TypeScript 2.6 支持在 .ts 文件中通过在报错一行上方使用 // @ts-ignore 来忽略错误.

例子

if (false) {
    // @ts-ignore: 无法被执行的代码的错误
    console.log("hello");
} 

// @ts-ignore 注释会忽略下一行中产生的所有错误. 建议实践中在 @ts-ignore 之后添加相关提示, 解释忽略了什么错误.

请注意, 这个注释仅会隐藏报错, 并且我们建议你极少使用这一注释.

更快的 tsc --watch

TypeScript 2.6 带来了更快的 --watch 实现. 新版本优化了使用 ES 模块的代码的生成和检查. 在一个模块文件中检测到的改变只会使改变的模块, 以及依赖它的文件被重新生成, 而不再是整个项目. 有大量文件的项目应该从这一改变中获益最多.

这一新的实现也为 tsserver 中的监听带来了性能提升. 监听逻辑被完全重写以更快响应改变事件.

只写的引用现在会被标记未使用

TypeScript 2.6 加入了修正的 --noUnusedLocals--noUnusedParameters 编译选项实现. 只被写但从没有被读的声明现在会被标记未使用.

例子

下面 nm 都会被标记为未使用, 因为它们的值从未被读取. 之前 TypeScript 只会检查它们的值是否被引用.

function f(n: number) {
    n = 0;
}

class C {
    private m: number;
    constructor() {
        this.m = 0;
    }
} 

另外仅被自己内部调用的函数也会被认为是未使用的.

例子

function f() {
    f(); // 错误: 'f' 被声明, 但它的值从未被使用
} 

TypeScript 2.5

可选的 catch 语句变量

得益于 @tinganho 做的工作, TypeScript 2.5 实现了一个新的 ECMAScript 特性, 允许用户省略 catch 语句中的变量. 举例来说, 当使用 JSON.parse 时, 你可能需要将对应的函数调用放在 try/catch 中, 但是最后可能并不会用到输入有误时会抛出的 SyntaxError (语法错误).

let input = "...";
try {
    JSON.parse(input);
}
catch {
    // ^ 注意我们的 `catch` 语句并没有声明一个变量
    console.log("传入的 JSON 不合法\n\n" + input)
} 

checkJs/@ts-check 模式中的类型断言/转换语法

TypeScript 2.5 引入了在使用纯 JavaScript 的项目中断言表达式类型的能力. 对应的语法是 /** @type {...} */ 标注注释后加上被圆括号括起来, 类型需要被重新演算的表达式. 举例:

var x = /** @type {SomeType} */ (AnyParenthesizedExpression); 

包去重和重定向

在 TypeScript 2.5 中使用 Node 模块解析策略进行导入时, 编译器现在会检查文件是否来自 "相同" 的包. 如果一个文件所在的包的 package.json 包含了与之前读取的包相同的 nameversion, 那么 TypeScript 会将它重定向到最顶层的包. 这可以解决两个包可能会包含相同的类声明, 但因为包含 private 成员导致他们在结构上不兼容的问题.

这也带来一个额外的好处, 可以通过避免从重复的包中加载 .d.ts 文件减少内存使用和编译器及语言服务的运行时计算.

--preserveSymlinks (保留符号链接) 编译器选项

TypeScript 2.5 带来了 preserveSymlinks 选项, 它对应了 Node.js 中 --preserve-symlinks 选项的行为. 这一选项也会带来和 Webpack 的 resolve.symlinks 选项相反的行为 (也就是说, 将 TypeScript 的 preserveSymlinks 选项设置为 true 对应了将 Webpack 的 resolve.symlinks 选项设为 false, 反之亦然).

在这一模式中, 对于模块和包的引用 (比如 import 语句和 /// <reference type="..." /> 指令) 都会以相对符号链接文件的位置被解析, 而不是相对于符号链接解析到的路径. 更具体的例子, 可以参考 Node.js 网站的文档.


TypeScript 2.4 新特性一览

TypeScript 2.3 新特性一览

TypeScript 2.2 新特性一览

TypeScript 2.1 新特性一览

TypeScript 2.0 新特性一览


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK