TypeScript 2.8 在这里,我们带来一些你会十分喜爱的功能!
你熟悉 TypeScript 吗? 它是一种将可选的静态类型添加到JavaScript的语言。 这些静态类型会让你的代码不犯拼写错误以及其他的愚蠢错误。 基于围绕这些类型所构建的组件,还可以实现代码自动补全和项目索引等更多美妙的功能。 把你的代码交给 TypeScript 编译器运行后,将只剩下清晰,可读性强且符合标准的JavaScript代码。 也可以将你的代码重写为大部分仅支持 es5 甚至 es3 旧版浏览器所能运行的代码。想要了解 TypeScript 的更多相关信息 , 请查看我们的文档.
如果你现在急不可待,你可以通过 NuGet 下载或者运行以下指令:
npm install -g typescript
你能获得以下编辑器支持:
Visual Studio 2015 (requires update 3)
Visual Studio 2017 (requires 15.2 or later)
Visual Studio Code with the next release, or by following instructions here.
对 其他编辑器 的支持更新时间可能会不同, 但会尽快提供。
为了快速浏览我们在此版本中发布的内容,我们将其组合成列表放在一起便于浏览:
如果你升级版本,你需要记住一些细微部分的 调整 .
除此之外,让我们来看看 TypeScript 2.8带来的新功能吧!!
条件类型 (Conditional types)
条件类型是 TypeScript 中的一个新构造,它允许我们根据其他类型来选择类型。 它们的表现形式是:
A extends B ? C : D
其中a,b,c和d是所有类型。 你应将其看作 " 当类型a可赋值给b时,那么这种类型是c; 否则,它是d。" 如果你曾在JavaScript中使用过条件语法,你将会感到很熟悉。
我们举两个具体的例子:
interface Animal { live(): void; }interface Dog extends Animal { woof(): void; }// 类型是 'number'type Foo = Dog extends Animal ? number : string;// 类型是 'string'type Bar = RegExp extends Dog ? number : string;
你可能会想知道改变为什么会立即生效。我们可以知道 Foo
是数字,而 Bar
会是字符串,所以我们不妨明确地写出它。但条件类型的真正强大之处来自于它们与泛型的使用。
例如,让我们来看看以下几个方法:
interface Id { id: number, /* other fields */ }interface Name { name: string, /* other fields */ }declare function createLabel(id: number): Id;declare function createLabel(name: string): Name;declare function createLabel(name: string | number): Id | Name;
这些用于createlabel
的重载,向我们展示了一个根据其输入类型作出选择的单个javascript函数。请注意以下两点:
如果一个资源必须在整个api中反复进行同样的选择,这会变得很麻烦。
我们必须创建三个重载:一个用于确定类型的每个案例,另一个用于最常见的案例。 对于我们必须处理的其他情况,重载次数会呈指数级增长。
相反,我们可以使用条件类型将我们的两个重载忽略到一个,并创建一个类型别名,以便我们可以重用该逻辑。
type IdOrName<T extends number | string>=T extends number ? Id : Name; declare function createLabel<T extends number | string>(idOrName: T): T extends number ? Id : Name; let a = createLabel("typescript");// Namelet b = createLabel(2.8);// Idlet c = createLabel("" as any);// Id | Namelet d = createLabel("" as never);// never
如同JavaScript可以根据一个值的特性在运行时做出决定一般,条件类型让 TypeScript 根据其他类型的特征在类型系统中作出判断。
作为另一个例子,我们也可以编写一个名为flatten
的类型,将数组类型平移为它们的元素类型,但是另外保留它们:
// 如果我们有一个数组,当我们用 'number' 索引时,将会得到这个类型。 // 否则,单独留下类型。type Flatten`<T>` = T extends any[] ? T[number] : T;
在条件类型内的推断
条件类型也为我们提供了一种方法,通过使用 infer
关键字从我们在真正分支中比较的类型推断出来。例如,我们可以在flatten
中推断出元素类型,而不用去动手取出它
// 我们可以使用 '(infer U)[]' 而不是 'Array`<infer U>`'type Flatten`<T>` = T extends Array`<infer U>` ? U : T;
在这里,我们已经声明式地引入了一个名为u
的新的泛型类型变量,而不是指定如何检索 t
的元素类型。这使我们不再考虑如何获取想要的类型。
通过条件分配给联合体( unions )
当条件类型对单个类型参数起作用时,它们分布在各个联合体中。 所以在下面的例子中,Bar
的类型是 string [] | number []
因为Foo
应用于联合类型string |number
。
type Foo`<T>`= T extends any ? T[] : never; /** * Foo distributes on 'string | number' to the type * * (string extends any ? string[] : never) | * (number extends any ? number[] : never) * * which boils down to * * string[] | number[] */ type Bar = Foo<string | number>;
如果你想要避免在联合体(unions)中发布内容,则可以使用中括号括住“extends”关键字的两侧:
type Foo`<T>` = [T] extends [any] ? T[] : never;// Boils down to Array<string | number>type Bar = Foo<string | number>;
虽然条件类型起初可能看起来有点吓人,但当你需要进一步推动类型系统以获得准确类型时,我们相信它们将为你带来大量的灵活性。
新的内置助手
TypeScript 2.8 在lib.d.ts
中提供了几种利用条件类型的新类型别名:
// 这些都已经内置到lib.d.ts中!/** * Exclude from T those types that are assignable to U */type Exclude`<T, U>` = T extends U ? never : T;/** * Extract from T those types that are assignable to U */type Extract`<T, U>` = T extends U ? T : never;/** * Exclude null and undefined from T */type NonNullable`<T>` = T extends null | undefined ? never : T;/** * Obtain the return type of a function type */type ReturnType`<T extends (...args: any[]) =>` any> = T extends (...args: any[]) => infer R ? R : any;/** * Obtain the return type of a constructor function type */type InstanceType`<T extends new (...args: any[]) =>` any> = T extends new (...args: any[]) => infer R ? R : any;
而Nonnullable
,Returntype
和Instancetype
相对不言而喻,Exclude
和Extract
则更有趣一些。
Extract
从第一个参数中选择用于给第二个参数分配的类型:
// string[] | number[]type Foo = Extract<boolean | string[] | number[], any[]>;
Exclude
则相反,它从第一个参数中删除不能分配给第二个参数的类型 :
// booleantype Bar = Exclude<boolean | string[] | number[], any[]>;
只有声明才会发出
感谢来自Manoj Patel的一个pull请求,TypeScript 现在提供了一个--emitDeclarationOnly
标志,当你有一个发送JavaScript文件的替代构建步骤时,可以使用这个标志,但需要单独发出声明文件。 在这种模式下,不会生成JavaScript文件和源代码文件;只是可以用于资源使用者的.d.ts
文件。
其中一个用例就是使用备用编译器来处理TypeScript,例如babel 7.对于利用此标志的存储库示例,check out urql from Formidable Labs,或查看our Babel starter repo。
@jsx
预处理指令(pragma comments)
通常,jsx的用户希望将其jsx标记重写为React.createElement
。然而,如果你使用的库有类似React的工厂api,比如Preact, Stencil, Inferno, Cycle其他等,你可能想稍微调整一下。
曾经,TypeScript 只允许用户使用jsxFactory
选项(以及不赞成使用的reactNamespace
选项)在全局级别控制jsx的发送。但如果你想在同一个应用程序中混合使用这些库,那 jsx 将无法使用。
幸运的是,TypeScript 2.8现在允许你通过在文件顶部添加一个// @ jsx
注释来逐个文件地设置你的jsx工厂。如果你在babel中使用了相同的功能,这应该看起来有些熟悉。
/** @jsx dom */import { dom } from "./renderer"<h></h>
上面的示例导入了一个名为dom
的函数,并使用jsx
编译指示选择dom
作为文件中所有jsx表达式的工厂。TypeScript 2.8 在编译为commonjs和es5时会将其重写为以下内容:
var renderer_1 = require("./renderer"); renderer_1.dom("h", null);
JSX
通过jsx工厂解决
目前,当typescript使用jsx时,它会查找全局JSX
命名空间来查找某些类型(例如“什么是jsx组件的类型?”)。在 TypeScript 2.8中,编译器将尝试根据jsx工厂的位置查找JSX
命名空间。例如,如果您的jsx工厂是React.createElement
,那么 TypeScript 将尝试首先解析React.JSX
,然后解析当前范围内的JSX
。
当混合和匹配不同库(例如: React 和 Preact)或特定库的不同版本(例如: React14和React16)时,这可能是有用的,因为将jsx名称空间放置在全局范围中可能导致问题。
今后,我们建议新的面向jsx的库避免将JSX
放在全局范围内,而是将它从相应的工厂函数的相同位置导出。然而,为了能够向后兼容,TypeScript 将在必要时继续回落到全局范围。
精确地控制映射类型的修饰符
TypeScript 的映射对象类型是一个非常强大的构造。它有个便利的功能:是允许用户创建新的类型,其中包含为其所有属性设置的修饰符。例如,以下类型创建一个基于T
的新类型,并且T
中的每个属性都变为只读
和可选(?
)。
// 创建一个包含 T 中所有属性的类型,, // 但标记为只读和可选.type ReadonlyAndPartial`<T>` = { readonly [P in keyof T]?: T[P] }
所以映射的对象类型可以添加修饰符,但直到此时,无法从T
删除remove修饰符。
TypeScript 2.8提供了用-
运算符去除映射类型中的修饰符的新语法,以及用+
运算符添加修饰符的新的更加明确的语法。例如:
type Mutable`<T>` = { -readonly [P in keyof T]: T[P] }interface Foo { readonly abc: number; def?: string; }// 'abc'is no longer read-only, but 'def'is still optional.type TotallyMutableFoo = Mutable`<Foo>`
在上面的例子中,Mutable
从它映射的类型的每个属性中删除readonly
。 同样,TypeScript 现在在lib.d.ts
中提供了一个新的Required
类型,用于从每个属性中删除选项:
/** * Make all properties in T required */type Required`<T>` = { [P in keyof T]-?: T[P]; }
当你想调出一个映射类型添加修饰符时,+
操作符可以很方便的实现。例如,我们从上面的ReadonlyAndPartial
可以定义如下:
type ReadonlyAndPartial`<T>` = { +readonly [P in keyof T]+?: T[P]; }
组织导入(Organize imports)
TypeScript 的语言服务现在提供了组织导入的功能。此功能将删除所有未使用的导入,按文件路径对现有导入进行排序,并对命名导入进行排序。
修复未初始化的属性
TypeScript 2.7引入了对类中未初始化属性的额外检查,感谢来自Wenlu Wang 的a pull request。 TypeScript 2.8带来了一些有用的快速修复,使其更容易添加到您的代码库中。
突破性的改变
在--noUnusedParameters
下检查未使用的类型参数
未使用的类型参数先前在--noUnusedLocals
下报告过,但是现在报告在--noUnusedParameters
下。
HTMLObjectElement
不再具有 alt
属性
这种行为不包含在 WHATWG 标准中。
下一步是什么?
我们希望 TypeScript 2.8能够进一步推动 envelope 提供一种真正代表 JavaScript 作为语言本质的类型系统。因此,我们相信我们可以在你编码的过程中,为你创造出一种更高效、更快乐的体验。
在接下来的几周内,我们将更清楚地了解 TypeScript 2.9的存储情况,但是和往常一样,你可以留意 TypeScript 路线图 ,看看我们正在为下一个版本做些什么。你也可以尝试我们的夜间发布,今天就能体验未来!例如,通用的jsx元素已经出现在 TypeScript 最近的夜间版本中!
让我们知道你对这个版本的看法,请写在Twitter 上或者在下面的评论中, 并随时向我们提交建议和 a GitHub issue.
可劲儿造吧!(Happy Hacking!)