Skip to content
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待

TypeScript 1.8

类型参数作为约束

使用 TypeScript 1.8,类型参数约束可以引用同一类型参数列表中的类型参数成为可能。 以前这是错误的。 这种能力通常被称为 F-Bounded 多态性

示例
ts
function assign<T 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 }); // 错误

控制流分析错误

TypeScript 1.8 引入了控制流分析,以帮助捕获用户容易遇到的常见错误。 请继续阅读以获取更多详细信息,并查看这些错误的实际效果:

cfa

无法访问的代码

保证在运行时不会执行的语句现在被正确标记为无法访问的代码错误。 例如,跟在无条件的 returnthrowbreakcontinue 语句之后的语句被认为是无法访问的。 使用 allowUnreachableCode 可以禁用无法访问代码的检测和报告。

示例

这是一个简单的无法访问代码错误示例:

ts
function f(x) {
  if (x) {
    return true;
  } else {
    return false;
  }

  x = 0; // 错误:检测到无法访问的代码。
}

此功能捕获的一个更常见的错误是在 return 语句后添加换行符:

ts
function f() {
  return; // 在换行符处触发自动分号插入
  {
    x: "string"; // 错误:检测到无法访问的代码。
  }
}

由于 JavaScript 在行尾自动终止 return 语句,对象字面量变成了一个块。

未使用的标签

未使用的标签也会被标记。 就像无法访问的代码检查一样,这些默认是开启的; 使用 allowUnusedLabels 可以停止报告这些错误。

示例
ts
loop: while (x > 0) {
  // 错误:未使用的标签。
  x++;
}

隐式返回

在 JS 中,具有未返回值的代码路径的函数会隐式返回 undefined。 现在编译器可以将这些标记为隐式返回。 该检查默认关闭;使用 noImplicitReturns 开启它。

示例
ts
function f(x) {
  // 错误:并非所有代码路径都返回值。
  if (x) {
    return false;
  }

  // 隐式返回 `undefined`
}

Case 子句的贯穿

当 switch 语句中的 case 子句非空时,TypeScript 可以报告贯穿错误。 此检查默认关闭,可以使用 noFallthroughCasesInSwitch 启用。

示例

使用 noFallthroughCasesInSwitch,此示例将触发错误:

ts
switch (x % 2) {
  case 0: // 错误:switch 中的贯穿 case。
    console.log("even");

  case 1:
    console.log("odd");
    break;
}

然而,在以下示例中,不会报告错误,因为贯穿的 case 是空的:

ts
switch (x % 3) {
  case 0:
  case 1:
    console.log("Acceptable");
    break;

  case 2:
    console.log("This is *two much*!");
    break;
}

React 中的函数组件

TypeScript 现在支持函数组件。 这些是轻量级组件,可以轻松组合其他组件:

ts
// 使用参数解构和默认值轻松定义 'props' 类型
const Greeter = ({ name = "world" }) => <div>Hello, {name}!</div>;

// 属性得到验证
let example = <Greeter name="TypeScript 1.8" />;

为了支持此功能和简化的 props,请务必使用最新版本的 react.d.ts

React 中简化的 props 类型管理

在 TypeScript 1.8 中,使用最新版本的 react.d.ts(见上文),我们还大大简化了 props 类型的声明。

具体来说:

  • 你不再需要显式声明 refkey 或扩展 React.Props
  • refkey 属性将以正确的类型出现在所有组件上
  • 在无状态函数组件的实例上正确禁止使用 ref 属性

从模块增强全局/模块作用域

用户现在可以声明他们想要进行的任何增强,或者任何其他消费者已经进行的增强,到一个现有模块。 模块增强看起来像普通的环境模块声明(即 declare module "foo" { } 语法),并且直接嵌套在你自己的模块中,或者嵌套在另一个顶级环境外部模块中。

此外,TypeScript 还具有 declare global { } 形式的全局增强概念。 这允许模块在必要时增强全局类型,如 Array

模块增强的名称使用与 importexport 声明中模块说明符相同的规则集进行解析。 模块增强中的声明与任何现有声明合并,就像它们在同一个文件中声明一样。

模块增强和全局增强都不能向顶级作用域添加新项——它们只能“修补”现有声明。

示例

这里 map.ts 可以声明它将在内部修补 observable.ts 中的 Observable 类型,并为其添加 map 方法。

ts
// observable.ts
export class Observable<T> {
  // ...
}
ts
// map.ts
import { Observable } from "./observable";

// 为 "./observable" 创建增强
declare module "./observable" {

    // 通过接口合并增强 'Observable' 类定义
    interface Observable<T> {
        map<U>(proj: (el: T) => U): Observable<U>;
    }

}

Observable.prototype.map = /*...*/;
ts
// consumer.ts
import { Observable } from "./observable";
import "./map";

let o: Observable<number>;
o.map((x) => x.toFixed());

类似地,可以使用 declare global 声明从模块增强全局作用域:

示例
ts
// 确保这被视为一个模块。
export {};

declare global {
  interface Array<T> {
    mapToNumbers(): number[];
  }
}

Array.prototype.mapToNumbers = function () {
  /* ... */
};

字符串字面量类型

API 期望特定值使用一组特定字符串是很常见的。 例如,考虑一个 UI 库,它可以在控制动画的“缓动”的同时在屏幕上移动元素。

ts
declare class UIElement {
  animate(options: AnimationOptions): void;
}

interface AnimationOptions {
  deltaX: number;
  deltaY: number;
  easing: string; // 可以是 "ease-in", "ease-out", "ease-in-out"
}

然而,这容易出错——没有什么能阻止用户意外拼错一个有效的缓动值:

ts
// 没有错误
new UIElement().animate({ deltaX: 100, deltaY: 100, easing: "ease-inout" });

使用 TypeScript 1.8,我们引入了字符串字面量类型。 这些类型的编写方式与字符串字面量相同,但位于类型位置。

用户现在可以确保类型系统会捕获此类错误。 这是我们使用字符串字面量类型的新 AnimationOptions

ts
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 1.8 改进了涉及源和目标侧都是联合或交集类型的类型推断。 例如,当从 string | string[] 推断为 string | T 时,我们将类型简化为 string[]T,从而为 T 推断出 string[]

示例
ts
type Maybe<T> = T | void;

function isDefined<T>(x: Maybe<T>): x is T {
  return x !== undefined && x !== null;
}

function isUndefined<T>(x: Maybe<T>): x is void {
  return x === undefined || x === null;
}

function getOrElse<T>(x: Maybe<T>, defaultValue: T): T {
  return isDefined(x) ? x : defaultValue;
}

function test1(x: Maybe<string>) {
  let x1 = getOrElse(x, "Undefined"); // string
  let x2 = isDefined(x) ? x : "Undefined"; // string
  let x3 = isUndefined(x) ? "Undefined" : x; // string
}

function test2(x: Maybe<number>) {
  let x1 = getOrElse(x, -1); // number
  let x2 = isDefined(x) ? x : -1; // number
  let x3 = isUndefined(x) ? -1 : x; // number
}

使用 --outFile 连接 AMDSystem 模块

outFile--module amd--module system 一起使用将把编译中的所有模块连接到一个包含多个模块闭包的输出文件中。

每个模块的模块名称将根据其相对于 rootDir 的位置计算。

示例
ts
// 文件 src/a.ts
import * as B from "./lib/b";
export function createA() {
  return B.createB();
}
ts
// 文件 src/lib/b.ts
export function createB() {
  return {};
}

结果:

js
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;
});

支持与 SystemJS 的 default 导入互操作

像 SystemJS 这样的模块加载器包装 CommonJS 模块,并将它们暴露为 default ES6 导入。这使得在 SystemJS 和 CommonJS 模块实现之间共享定义文件变得不可能,因为模块的形状因加载器而异。

设置新的编译器标志 allowSyntheticDefaultImports 表示模块加载器执行某种合成默认导入成员创建,这在导入的 .ts 或 .d.ts 中没有指示。编译器将推断存在一个具有整个模块本身形状的 default 导出。

System 模块默认启用此标志。

允许在循环中捕获 let/const

以前这是一个错误,现在在 TypeScript 1.8 中得到支持。 循环中的 let/const 声明并在函数中捕获现在被正确输出,以匹配 let/const 的新鲜度语义。

示例
ts
let list = [];
for (let i = 0; i < 5; i++) {
  list.push(() => i);
}

list.forEach((f) => console.log(f()));

被编译为:

js
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());
});

结果输出:

cmd
0
1
2
3
4

改进对 for..in 语句的检查

以前,for..in 变量的类型被推断为 any;这允许编译器忽略 for..in 主体中的无效使用。

从 TypeScript 1.8 开始:

  • for..in 语句中声明的变量的类型隐式为 string
  • 当具有类型 T 的数字索引签名的对象(例如数组)被包含 for..in 语句中的 for..in 变量索引时,对于具有数字索引签名且没有字符串索引签名的对象(再次例如数组),产生的值的类型为 T

示例
ts
var a: MyObject[];
for (var x in a) {
  // x 的类型隐式为 string
  var obj = a[x]; // obj 的类型为 MyObject
}

模块现在以 "use strict"; 序言输出

根据 ES6,模块始终以严格模式解析,但对于非 ES6 目标,这在生成的代码中并未得到遵守。从 TypeScript 1.8 开始,输出的模块始终处于严格模式。在大多数代码中,这不应该有任何可见的变化,因为 TS 将大多数严格模式错误视为编译时错误,但这意味着一些在 TS 代码中过去在运行时静默失败的事情,比如赋值给 NaN,现在将明显失败。你可以参考 MDN 文章 了解严格模式与非严格模式之间差异的详细列表。

使用 --allowJs 包含 .js 文件

项目中通常存在可能不是用 TypeScript 编写的外部源文件。 或者,你可能正在将 JS 代码库转换为 TS 的过程中,但仍然希望将所有 JS 代码与新 TS 代码的输出捆绑到一个文件中。

.js 文件现在可以作为 tsc 的输入。 TypeScript 编译器检查输入的 .js 文件是否有语法错误,并根据 targetmodule 标志输出有效输出。 输出也可以与其他 .ts 文件组合。 源映射仍然为 .js 文件生成,就像为 .ts 文件一样。

使用 --reactNamespace 自定义 JSX 工厂

传递 --reactNamespace <JSX factory Name>--jsx react 一起允许使用与默认 React 不同的 JSX 工厂。

新的工厂名称将用于调用 createElement__spread 函数。

示例
ts
import { jsxFactory } from "jsxFactory";

var div = <div>Hello JSX!</div>;

使用以下命令编译:

shell
tsc --jsx react --reactNamespace jsxFactory --m commonJS

结果:

js
"use strict";
var jsxFactory_1 = require("jsxFactory");
var div = jsxFactory_1.jsxFactory.createElement("div", null, "Hello JSX!");

基于 this 的类型守卫

TypeScript 1.8 将用户定义的类型守卫函数扩展到类和接口方法。

this is T 现在作为类和接口中方法的有效返回类型注解。 当在类型收窄位置(例如 if 语句)使用时,调用表达式目标对象的类型将被收窄为 T

示例
ts
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
}

官方 TypeScript NuGet 包

从 TypeScript 1.8 开始,官方的 NuGet 包可用于 TypeScript 编译器(tsc.exe)以及 MSBuild 集成(Microsoft.TypeScript.targetsMicrosoft.TypeScript.Tasks.dll)。

稳定包可在此处获取:

此外,与夜间 npm 包对应的夜间 NuGet 包可在 myget 上获取:

来自 tsc 的更美观的错误消息

我们知道大量的单色输出对眼睛来说可能有点困难。 颜色可以帮助区分消息的开始和结束,当错误输出变得铺天盖地时,这些视觉线索很重要。

只需传递 pretty 命令行选项,TypeScript 就会提供更彩色的输出,并带有关于错误发生位置的上下文信息。

在 ConEmu 中展示漂亮的错误消息

VS 2015 中 JSX 代码的颜色化

使用 TypeScript 1.8,JSX 标签现在在 Visual Studio 2015 中被分类和着色。

jsx

可以通过 工具->选项->环境->字体和颜色页面更改 VB XML 颜色和字体设置来进一步自定义分类。

--project-p)标志现在可以接受任何文件路径

--project 命令行选项最初只能接受包含 tsconfig.json 的文件夹路径。 考虑到构建配置的不同场景,允许 --project 指向任何其他兼容的 JSON 文件是有意义的。 例如,用户可能希望为 Node 5 使用带有 CommonJS 模块的 ES2015 目标,而为浏览器使用带有 AMD 模块的 ES5 目标。 通过这项新工作,用户可以仅使用 tsc 轻松管理两个独立的构建目标,而无需执行诸如将 tsconfig.json 文件放在单独目录中的 hacky 变通方法。

如果给定一个目录,旧行为仍然相同——编译器将尝试在目录中查找名为 tsconfig.json 的文件。

允许在 tsconfig.json 中添加注释

能够记录你的配置总是很棒的! tsconfig.json 现在接受单行和多行注释。

json
{
  "compilerOptions": {
    "target": "ES2015", // 运行在 node v5 上,耶!
    "sourceMap": true // 使调试更容易
  },
  /*
   * 排除的文件
   */
  "exclude": ["file.d.ts"]
}

支持输出到 IPC 驱动的文件

TypeScript 1.8 允许用户将 outFile 参数与特殊的文件系统实体(如命名管道、设备等)一起使用。

例如,在许多类 Unix 系统上,标准输出流可以通过文件 /dev/stdout 访问。

shell
tsc foo.ts --outFile /dev/stdout

这也可以用于在命令之间管道输出。

例如,我们可以将输出的 JavaScript 管道传递给像 pretty-js 这样的美化打印器:

shell
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 文件,而是可以添加多个文件,每个文件都将作为项目的一部分进行构建。 这允许你在不创建多个不同项目的情况下,为应用程序的不同部分分离配置。

在 Visual Studio 中展示 tsconfig.json

当你添加 tsconfig.json 文件时,我们还会禁用项目属性页。 这意味着所有配置更改都必须在 tsconfig.json 文件本身中进行。

几个限制

  • 如果添加了 tsconfig.json 文件,则认为不属于该上下文的 TypeScript 文件不会被编译。
  • Apache Cordova 应用程序仍然存在单个 tsconfig.json 文件的限制,该文件必须位于根目录或 scripts 文件夹中。
  • 大多数项目类型中没有 tsconfig.json 模板。